This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

201
modules/jdbc-pool/LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

6
modules/jdbc-pool/NOTICE Normal file
View File

@@ -0,0 +1,6 @@
Apache Tomcat JDBC Pool
Copyright 2008-2020 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).

View File

@@ -0,0 +1,102 @@
# -----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# build.properties.sample
#
# This is an example "build.properties" file, used to customize building
# Tomcat JDBC Pool for your local environment. It defines the location of all external
# modules that Tomcat JDBC Pool depends on. Copy this file to "build.properties"
# in the top-level source directory, and customize it as needed.
# -----------------------------------------------------------------------------
# ----- Version Control Flags -----
version.major=1
version.minor=1
version.build=0
version.patch=1
version.suffix=
# ----- Default Base Path for Dependent Packages -----
# Please note this path must be absolute, not relative,
# as it is referenced with different working directory
# contexts by the various build scripts.
base.path=${basedir}/includes
compile.source=1.7
compile.target=1.7
compile.release=7
compile.debug=true
# Do not pass -deprecation (-Xlint:deprecation) flag to javac
compile.deprecation=false
# ----- Settings for Junit test database.
# Common settings
testdb.username=root
testdb.password=password
# H2
testdb.url=jdbc:h2:~/.h2/test;QUERY_TIMEOUT=0;DB_CLOSE_ON_EXIT=TRUE;LOCK_TIMEOUT=50000;DEFAULT_LOCK_TIMEOUT=50000
testdb.driverClassName=org.h2.Driver
testdb.validationQuery=SELECT 1
# MySQL
#testdb.url=jdbc:mysql://localhost:3306/mysql?autoReconnect=true
#testdb.driverClassName=com.mysql.jdbc.Driver
#testdb.validationQuery=SELECT 1
# Derby
#testdb.url=jdbc:derby:derbyDB;create=true
#testdb.driverClassName=org.apache.derby.jdbc.EmbeddedDriver
#testdb.validationQuery=VALUES 1
# JUnit Unit Test Suite
junit.version=4.11
junit.home=${base.path}/junit-${junit.version}
junit.jar=${junit.home}/junit-${junit.version}.jar
junit.loc=https://repo.maven.apache.org/maven2/junit/junit/${junit.version}/junit-${junit.version}.jar
# Hamcrest Library, used by JUnit
hamcrest.version=1.3
hamcrest.home=${base.path}/hamcrest-${hamcrest.version}
hamcrest.jar=${hamcrest.home}/hamcrest-core-${hamcrest.version}.jar
hamcrest.loc=https://repo.maven.apache.org/maven2/org/hamcrest/hamcrest-core/${hamcrest.version}/hamcrest-core-${hamcrest.version}.jar
mysql.home=${base.path}/mysql-connector-java-5.1.12
mysql.jar=${mysql.home}/mysql-connector-java-5.1.12-bin.jar
mysql.loc=http://mysql.mirrors.hoobly.com/Downloads/Connector-J/mysql-connector-java-5.1.12.zip
tomcat.version=8.0.14
tomcat.home=${base.path}/apache-tomcat-${tomcat.version}
tomcat.dbcp.jar=${tomcat.home}/lib/tomcat-dbcp.jar
tomcat.juli.jar=${tomcat.home}/bin/tomcat-juli.jar
tomcat.loc=https://archive.apache.org/dist/tomcat/tomcat-8/v${tomcat.version}/bin/apache-tomcat-${tomcat.version}.zip
tomcat.project.loc=https://svn.apache.org/repos/asf/tomcat/trunk/webapps/docs/project.xml
tomcat.project.dest=${base.path}/project.xml
tomcat.xsl.loc=https://svn.apache.org/repos/asf/tomcat/trunk/webapps/docs/tomcat-docs.xsl
tomcat.xsl.dest=${base.path}/tomcat-docs.xsl
derby.home=${base.path}/db-derby-10.5.1.1-bin
derby.loc=https://archive.apache.org/dist/db/derby/db-derby-10.5.1.1/db-derby-10.5.1.1-bin.tar.gz
derby.jar=${derby.home}/lib/derby.jar
h2.version=1.2.129
h2.home=${base.path}/h2-${h2.version}
h2.loc=https://repo.maven.apache.org/maven2/com/h2database/h2/1.2.129/h2-1.2.129.jar
h2.jar=${h2.home}/h2-1.2.129.jar

529
modules/jdbc-pool/build.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE document [
<!ENTITY project SYSTEM "@TOMCAT_PROJECT_DEST@">
]>
<?xml-stylesheet type="text/xsl" href="package.xsl"?>
<document url="changelog.html">
&project;
<properties>
<author email="fhanik@apache.org">Filip Hanik</author>
<title>Changelog</title>
</properties>
<body>
<section name="Tomcat JDBC Connection Pool - Apache Tomcat 7.0.19 and later">
<p>
Starting with Apache Tomcat 7.0.19 in July 2011, Tomcat JDBC Connection Pool
is built and released as a component in official releases of Tomcat.
The changes are now listed in "jdbc-pool" sections of Apache Tomcat
changelog file. This changelog file is obsolete.
</p>
</section>
<section name="Tomcat JDBC Connection Pool 1.1.0.0">
<subsection name="pool">
<changelog>
<add><rev>1207712</rev> Pool cleaner should be a global thread, not spawn one thread per connection pool. (fhanik)</add>
<fix><rev>1073531</rev> <bug>50805</bug> Only initialize connections once when async (fhanik)</fix>
<fix><rev>1076380</rev> <bug>50857</bug> Correctly handle timeouts when the pool is busy when async (fhanik)</fix>
<add>Added QueryTimeoutInterceptor to be able to configure timeouts on running queries automatically.</add>
</changelog>
</subsection>
</section>
<section name="Tomcat JDBC Connection Pool 1.0.9.4">
<subsection name="pool">
<changelog>
<fix><rev>1069864</rev> <bug>50759</bug> Correctly set validation timestamp when using external validator.(fhanik)</fix>
</changelog>
</subsection>
</section>
<section name="Tomcat JDBC Connection Pool 1.0.9.3">
<subsection name="pool">
<changelog>
<fix><rev>1060998</rev> <bug>50613</bug> Fix concurrency issue around pool size calculation.(fhanik)</fix>
</changelog>
</subsection>
</section>
<section name="Tomcat JDBC Connection Pool 1.0.9.2">
<subsection name="pool">
<changelog>
<fix><rev>1057743</rev> Make sure passwords are masked.(fhanik)</fix>
</changelog>
</subsection>
</section>
<section name="Tomcat JDBC Connection Pool 1.0.9.0">
<subsection name="pool">
<changelog>
<fix><rev>997321</rev> Ensure threads borrowing connections do not
get stuck waiting for a new connection if a connection is released in
another thread. (markt)</fix>
<fix><rev>995432</rev> Make interceptor class names, property names
and property values tolerant of whitespace by trimming the values before
use. (markt)</fix>
<fix><rev>995091</rev> <bug>49831</bug> Make sure pooled XAConnections are
closed when the connection pool shuts down. Patch provided by Daniel
Mikusa. (markt)</fix>
<update><rev>995087</rev> Code clean-up. Remove some unused code. (markt)
</update>
<update><rev>995083</rev> Update to Tomcat 6.0.29 (for JULI). (markt)
</update>
<update><rev>992409</rev> Code clean-up. Reduce sequences of three or more
blank lines to two blank lines. (markt)</update>
<add><rev>952811</rev>, <rev>995095</rev> <bug>48814</bug> Add Validator
interface and allow users to configure a Validator class name. Patch
provided by Matt Passell. (markt)</add>
<update><rev>948073</rev> Code clean-up. Remove unused imports. (markt)
</update>
<fix><rev>943434</rev> <bug>49224</bug> Only try setting the username and
password if they are non-null. Patch provided by Matt Passell. (markt)
</fix>
<fix><rev>943032</rev> <bug>49269</bug> Set maxIdle to maxActive by
default to prevent warning on start when maxIdle > maxActive. Patch
provided by Matt Passell. (markt)</fix>
<fix><rev>940574</rev> <bug>49241</bug> Don&apos;t ignore the
suspectTimeout property. (fhanik)</fix>
<fix><rev>939320</rev> Fix svn:keywords for property replacement.
(kkolinko)</fix>
<add><rev>931550</rev>, <rev>934651</rev>, <rev>934677</rev> Add a
statement cache. (fhanik)</add>
<update><rev>919076</rev> Improve XA support. (fhanik)</update>
<fix><rev>915940</rev> <bug>48392</bug> Add an interceptor to wrap
Statements and ResultSets to prevent access to the physical connection.
(fhanik)</fix>
<fix><rev>912026</rev> Call <code>setTransactionIsolation()</code> before
anything else as some drivers require this to be the first call. (fhanik)
</fix>
<update><rev>900017</rev> Update Javadoc for XADataSource. (kkolinko)
</update>
</changelog>
</subsection>
</section>
<section name="Tomcat JDBC Connection Pool prior to 1.0.9.0 (incomplete)">
<subsection name="pool">
<changelog>
<update><rev>720253</rev> Document how to use interceptors</update>
<update><rev>717972</rev> Added an interceptor that will clean up non closed statements when a connection is returned to the pool. (<code>org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer</code>)</update>
<update><rev>713763</rev> Improve connection state handling</update>
<fix><rev>713763</rev> Improve connection state handling</fix>
</changelog>
</subsection>
</section>
</body>
</document>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,249 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--
Stylesheet that generates "package.html" for Javadoc tool
from jdbc-pool.xml documentation file.
It is based on "tomcat-docs" stylesheet, but it needs to avoid
generating complicated headers and footers, as those cannot be
digested by Javadoc tool and break layout of javadoc pages.
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<!-- Output method -->
<xsl:output method="html"
encoding="UTF-8"
indent="no"/>
<!-- Defined parameters (overrideable) -->
<xsl:param name="relative-path" select="'.'"/>
<xsl:param name="void-image" select="'/images/void.gif'"/>
<xsl:param name="standalone" select="''"/>
<xsl:param name="buglink" select="'https://bz.apache.org/bugzilla/show_bug.cgi?id='"/>
<xsl:param name="revlink" select="'https://svn.apache.org/viewvc?view=rev&amp;rev='"/>
<!-- Defined variables (non-overrideable) -->
<xsl:variable name="body-bg" select="'#ffffff'"/>
<xsl:variable name="body-fg" select="'#000000'"/>
<xsl:variable name="body-link" select="'#525D76'"/>
<xsl:variable name="banner-bg" select="'#525D76'"/>
<xsl:variable name="banner-fg" select="'#ffffff'"/>
<xsl:variable name="sub-banner-bg" select="'#828DA6'"/>
<xsl:variable name="sub-banner-fg" select="'#ffffff'"/>
<xsl:variable name="source-color" select="'#023264'"/>
<xsl:variable name="attributes-color" select="'#023264'"/>
<xsl:variable name="table-th-bg" select="'#039acc'"/>
<xsl:variable name="table-td-bg" select="'#a0ddf0'"/>
<!-- Process an entire document into an HTML page -->
<xsl:template match="document">
<xsl:variable name="project"
select="document('project.xml')/project"/>
<html>
<head>
<title><xsl:value-of select="project/title"/> - <xsl:value-of select="properties/title"/></title>
</head>
<body bgcolor="{$body-bg}" text="{$body-fg}" link="{$body-link}"
alink="{$body-link}" vlink="{$body-link}">
<h2><xsl:value-of select="properties/title"/>.</h2>
<xsl:apply-templates select="body/section"/>
</body>
</html>
</xsl:template>
<!-- Process a documentation section -->
<xsl:template match="section">
<xsl:variable name="name">
<xsl:value-of select="@name"/>
</xsl:variable>
<table border="0" cellspacing="0" cellpadding="2">
<!-- Section heading -->
<tr><td bgcolor="{$banner-bg}">
<font color="{$banner-fg}" face="arial,helvetica.sanserif">
<a name="{$name}">
<strong><xsl:value-of select="@name"/></strong></a></font>
</td></tr>
<!-- Section body -->
<tr><td><blockquote>
<xsl:apply-templates/>
</blockquote></td></tr>
</table>
</xsl:template>
<!-- Process a documentation subsection -->
<xsl:template match="subsection">
<xsl:variable name="name">
<xsl:value-of select="@name"/>
</xsl:variable>
<table border="0" cellspacing="0" cellpadding="2">
<!-- Subsection heading -->
<tr><td bgcolor="{$sub-banner-bg}">
<font color="{$sub-banner-fg}" face="arial,helvetica.sanserif">
<a name="{$name}">
<strong><xsl:value-of select="@name"/></strong></a></font>
</td></tr>
<!-- Subsection body -->
<tr><td><blockquote>
<xsl:apply-templates/>
</blockquote></td></tr>
</table>
</xsl:template>
<!-- Process a source code example -->
<xsl:template match="source">
<xsl:variable name="void">
<xsl:value-of select="$relative-path"/><xsl:value-of select="$void-image"/>
</xsl:variable>
<div align="left">
<table cellspacing="4" cellpadding="0" border="0">
<tr>
<td bgcolor="{$source-color}" width="1" height="1">
<img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
</td>
<td bgcolor="{$source-color}" height="1">
<img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
</td>
<td bgcolor="{$source-color}" width="1" height="1">
<img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
</td>
</tr>
<tr>
<td bgcolor="{$source-color}" width="1">
<img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
</td>
<td bgcolor="#ffffff" height="1"><pre>
<xsl:value-of select="."/>
</pre></td>
<td bgcolor="{$source-color}" width="1">
<img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
</td>
</tr>
<tr>
<td bgcolor="{$source-color}" width="1" height="1">
<img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
</td>
<td bgcolor="{$source-color}" height="1">
<img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
</td>
<td bgcolor="{$source-color}" width="1" height="1">
<img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
</td>
</tr>
</table>
</div>
</xsl:template>
<!-- Process an attributes list with nested attribute elements -->
<xsl:template match="attributes">
<table border="1" cellpadding="5">
<tr>
<th width="15%" bgcolor="{$attributes-color}">
<font color="#ffffff">Attribute</font>
</th>
<th width="85%" bgcolor="{$attributes-color}">
<font color="#ffffff">Description</font>
</th>
</tr>
<xsl:for-each select="attribute">
<tr>
<td align="left" valign="center">
<xsl:if test="@required = 'true'">
<strong><code><xsl:value-of select="@name"/></code></strong>
</xsl:if>
<xsl:if test="@required != 'true'">
<code><xsl:value-of select="@name"/></code>
</xsl:if>
</td>
<td align="left" valign="center">
<xsl:apply-templates/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<!-- Process a properties list with nested property elements -->
<xsl:template match="properties">
<table border="1" cellpadding="5">
<tr>
<th width="15%" bgcolor="{$attributes-color}">
<font color="#ffffff">Property</font>
</th>
<th width="85%" bgcolor="{$attributes-color}">
<font color="#ffffff">Description</font>
</th>
</tr>
<xsl:for-each select="property">
<tr>
<td align="left" valign="center">
<code><xsl:value-of select="@name"/></code>
</td>
<td align="left" valign="center">
<xsl:apply-templates/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<!-- Fix relative links in printer friendly versions of the docs -->
<xsl:template match="a">
<xsl:variable name="href" select="@href"/>
<xsl:choose>
<xsl:when test="$standalone = 'standalone'">
<xsl:apply-templates/>
</xsl:when>
<xsl:when test="$href != ''">
<a href="{$href}"><xsl:apply-templates/></a>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="name" select="@name"/>
<a name="{$name}"><xsl:apply-templates/></a>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Link to a bug report -->
<xsl:template match="bug">
<xsl:variable name="link"><xsl:value-of select="$buglink"/><xsl:value-of select="text()"/></xsl:variable>
<a href="{$link}"><xsl:apply-templates/></a>
</xsl:template>
<!-- Link to a SVN revision report -->
<xsl:template match="rev">
<xsl:variable name="link"><xsl:value-of select="$revlink"/><xsl:value-of select="text()"/></xsl:variable>
<a href="{$link}"><xsl:apply-templates/></a>
</xsl:template>
<!-- Process everything else by just passing it through -->
<xsl:template match="*|@*">
<xsl:copy>
<xsl:apply-templates select="@*|*|text()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project name="Apache Tomcat JDBC Pool Documentation"
href="http://tomcat.apache.org/">
<title>Apache Tomcat JDBC Pool</title>
<logo href="/images/tomcat.gif">
The Apache Tomcat Servlet/JSP Container
</logo>
<body>
</body>
</project>

158
modules/jdbc-pool/pom.xml Normal file
View File

@@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache</groupId>
<artifactId>apache</artifactId>
<version>15</version>
</parent>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>8.0.15-SNAPSHOT</version>
<packaging>jar</packaging>
<name>jdbc-pool</name>
<url>https://people.apache.org/~fhanik/jdbc-pool/</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<mailingLists>
<mailingList>
<name>Development List</name>
<subscribe>dev-subscribe@tomcat.apache.org</subscribe>
<unsubscribe>dev-unsubscribe@tomcat.apache.org</unsubscribe>
<post>dev@tomcat.apache.org</post>
</mailingList>
<mailingList>
<name>Users List</name>
<subscribe>users-subscribe@tomcat.apache.org</subscribe>
<unsubscribe>users-unsubscribe@tomcat.apache.org</unsubscribe>
<post>users@tomcat.apache.org</post>
</mailingList>
</mailingLists>
<scm>
<connection>scm:svn:https://svn.apache.org/repos/asf/tomcat/trunk/modules/jdbc-pool</connection>
<developerConnection>scm:svn:https://svn.apache.org/repos/asf/tomcat/trunk/modules/jdbc-pool</developerConnection>
<url>https://svn.apache.org/viewvc/tomcat/trunk/modules/jdbc-pool</url>
</scm>
<dependencies>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>juli</artifactId>
<version>6.0.32</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>8.0.14</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.152</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<optimize>true</optimize>
<debug>true</debug>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.2</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,22 @@
Manifest-Version: 1.0
Export-Package: org.apache.tomcat.jdbc.naming;uses:="javax.naming,org.ap
ache.juli.logging,javax.naming.spi";version="@VERSION@",org.apache.tomc
at.jdbc.pool;uses:="org.apache.juli.logging,javax.sql,org.apache.tomcat
.jdbc.pool.jmx,javax.management,javax.naming,javax.naming.spi,org.apach
e.tomcat.jdbc.pool.interceptor";version="@VERSION@",org.apache.tomcat.j
dbc.pool.interceptor;uses:="org.apache.tomcat.jdbc.pool,org.apache.juli
.logging,javax.management.openmbean,javax.management";version="@VERSION@
",org.apache.tomcat.jdbc.pool.jmx;uses:="org.apache.tomcat.jdbc.pool,or
g.apache.juli.logging,javax.management";version="@VERSION@"
Bundle-Vendor: Apache Software Foundation
Bundle-Version: @VERSION@
Bundle-Name: Apache Tomcat JDBC Connection Pool
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.apache.tomcat.jdbc
Import-Package:
javax.management;version="0",
javax.management.openmbean;version="0",
javax.naming;version="0",
javax.naming.spi;version="0",
javax.sql;version="0",
org.apache.juli.logging;version="0"

View File

@@ -0,0 +1,233 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.naming;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.ClassLoaderUtil;
/**
* Simple way of configuring generic resources by using reflection.
* Example usage:
* <pre><code>
* &lt;Resource factory=&quot;org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory&quot;
* name=&quot;jdbc/test&quot;
* type=&quot;org.apache.derby.jdbc.ClientXADataSource&quot;
* databaseName=&quot;sample&quot;
* createDatabase=&quot;create&quot;
* serverName=&quot;localhost&quot;
* port=&quot;1527&quot;/&gt;
* </code></pre>
*
*/
public class GenericNamingResourcesFactory implements ObjectFactory {
private static final Log log = LogFactory.getLog(GenericNamingResourcesFactory.class);
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
if ((obj == null) || !(obj instanceof Reference)) {
return null;
}
Reference ref = (Reference) obj;
Enumeration<RefAddr> refs = ref.getAll();
String type = ref.getClassName();
Object o =
ClassLoaderUtil.loadClass(
type,
GenericNamingResourcesFactory.class.getClassLoader(),
Thread.currentThread().getContextClassLoader()).getConstructor().newInstance();
while (refs.hasMoreElements()) {
RefAddr addr = refs.nextElement();
String param = addr.getType();
String value = null;
if (addr.getContent()!=null) {
value = addr.getContent().toString();
}
if (setProperty(o, param, value)) {
} else {
log.debug("Property not configured["+param+"]. No setter found on["+o+"].");
}
}
return o;
}
@SuppressWarnings("null") // setPropertyMethodVoid can't be null when used
private static boolean setProperty(Object o, String name, String value) {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: setProperty(" +
o.getClass() + " " + name + "=" + value + ")");
String setter = "set" + capitalize(name);
try {
Method methods[] = o.getClass().getMethods();
Method setPropertyMethodVoid = null;
Method setPropertyMethodBool = null;
// First, the ideal case - a setFoo( String ) method
for (int i = 0; i < methods.length; i++) {
Class<?> paramT[] = methods[i].getParameterTypes();
if (setter.equals(methods[i].getName()) && paramT.length == 1
&& "java.lang.String".equals(paramT[0].getName())) {
methods[i].invoke(o, new Object[] { value });
return true;
}
}
// Try a setFoo ( int ) or ( boolean )
for (int i = 0; i < methods.length; i++) {
boolean ok = true;
if (setter.equals(methods[i].getName())
&& methods[i].getParameterTypes().length == 1) {
// match - find the type and invoke it
Class<?> paramType = methods[i].getParameterTypes()[0];
Object params[] = new Object[1];
// Try a setFoo ( int )
if ("java.lang.Integer".equals(paramType.getName())
|| "int".equals(paramType.getName())) {
try {
params[0] = Integer.valueOf(value);
} catch (NumberFormatException ex) {
ok = false;
}
// Try a setFoo ( long )
}else if ("java.lang.Long".equals(paramType.getName())
|| "long".equals(paramType.getName())) {
try {
params[0] = Long.valueOf(value);
} catch (NumberFormatException ex) {
ok = false;
}
// Try a setFoo ( boolean )
} else if ("java.lang.Boolean".equals(paramType.getName())
|| "boolean".equals(paramType.getName())) {
params[0] = Boolean.valueOf(value);
// Try a setFoo ( InetAddress )
} else if ("java.net.InetAddress".equals(paramType
.getName())) {
try {
params[0] = InetAddress.getByName(value);
} catch (UnknownHostException exc) {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: Unable to resolve host name:" + value);
ok = false;
}
// Unknown type
} else {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: Unknown type " +
paramType.getName());
}
if (ok) {
methods[i].invoke(o, params);
return true;
}
}
// save "setProperty" for later
if ("setProperty".equals(methods[i].getName())) {
if (methods[i].getReturnType()==Boolean.TYPE){
setPropertyMethodBool = methods[i];
}else {
setPropertyMethodVoid = methods[i];
}
}
}
// Ok, no setXXX found, try a setProperty("name", "value")
if (setPropertyMethodBool != null || setPropertyMethodVoid != null) {
Object params[] = new Object[2];
params[0] = name;
params[1] = value;
if (setPropertyMethodBool != null) {
try {
return ((Boolean) setPropertyMethodBool.invoke(o, params)).booleanValue();
}catch (IllegalArgumentException biae) {
//the boolean method had the wrong
//parameter types. lets try the other
if (setPropertyMethodVoid!=null) {
setPropertyMethodVoid.invoke(o, params);
return true;
}else {
throw biae;
}
}
} else {
setPropertyMethodVoid.invoke(o, params);
return true;
}
}
} catch (IllegalArgumentException ex2) {
log.warn("IAE " + o + " " + name + " " + value, ex2);
} catch (SecurityException ex1) {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: SecurityException for " +
o.getClass() + " " + name + "=" + value + ")", ex1);
} catch (IllegalAccessException iae) {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: IllegalAccessException for " +
o.getClass() + " " + name + "=" + value + ")", iae);
} catch (InvocationTargetException ie) {
Throwable cause = ie.getCause();
if (cause instanceof ThreadDeath) {
throw (ThreadDeath) cause;
}
if (cause instanceof VirtualMachineError) {
throw (VirtualMachineError) cause;
}
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: InvocationTargetException for " +
o.getClass() + " " + name + "=" + value + ")", ie);
}
return false;
}
public static String capitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toUpperCase(chars[0]);
return new String(chars);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
public class ClassLoaderUtil {
private static final Log log = LogFactory.getLog(ClassLoaderUtil.class);
private static final boolean onlyAttemptFirstLoader =
Boolean.parseBoolean(System.getProperty("org.apache.tomcat.jdbc.pool.onlyAttemptCurrentClassLoader", "false"));
public static Class<?> loadClass(String className, ClassLoader... classLoaders) throws ClassNotFoundException {
ClassNotFoundException last = null;
StringBuilder errorMsg = null;
for (ClassLoader cl : classLoaders) {
try {
if (cl!=null) {
if (log.isDebugEnabled()) {
log.debug("Attempting to load class["+className+"] from "+cl);
}
return Class.forName(className, true, cl);
} else {
throw new ClassNotFoundException("Classloader is null");
}
} catch (ClassNotFoundException x) {
last = x;
if (errorMsg==null) {
errorMsg = new StringBuilder();
} else {
errorMsg.append(';');
}
errorMsg.append("ClassLoader:");
errorMsg.append(cl);
}
if (onlyAttemptFirstLoader) {
break;
}
}
throw new ClassNotFoundException("Unable to load class: "+className+" from "+errorMsg, last);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,145 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
import java.util.Hashtable;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.jmx.JmxUtil;
/**
* A DataSource that can be instantiated through IoC and implements the DataSource interface
* since the DataSourceProxy is used as a generic proxy.
* The DataSource simply wraps a {@link ConnectionPool} in order to provide a standard interface to the user.
* @version 1.0
*/
public class DataSource extends DataSourceProxy implements javax.sql.DataSource,MBeanRegistration, org.apache.tomcat.jdbc.pool.jmx.ConnectionPoolMBean, javax.sql.ConnectionPoolDataSource {
private static final Log log = LogFactory.getLog(DataSource.class);
/**
* Constructor for reflection only. A default set of pool properties will be created.
*/
public DataSource() {
super();
}
/**
* Constructs a DataSource object wrapping a connection
* @param poolProperties The pool properties
*/
public DataSource(PoolConfiguration poolProperties) {
super(poolProperties);
}
//===============================================================================
// JMX Operations - Register the actual pool itself under the tomcat.jdbc domain
//===============================================================================
protected volatile ObjectName oname = null;
/**
* Unregisters the underlying connection pool mbean.<br>
* {@inheritDoc}
*/
@Override
public void postDeregister() {
if (oname!=null) unregisterJmx();
}
/**
* no-op<br>
* {@inheritDoc}
*/
@Override
public void postRegister(Boolean registrationDone) {
// NOOP
}
/**
* no-op<br>
* {@inheritDoc}
*/
@Override
public void preDeregister() throws Exception {
// NOOP
}
/**
* If the connection pool MBean exists, it will be registered during this operation.<br>
* {@inheritDoc}
*/
@Override
public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {
try {
if ( isJmxEnabled() ) {
this.oname = createObjectName(name);
if (oname!=null) registerJmx();
}
}catch (MalformedObjectNameException x) {
log.error("Unable to create object name for JDBC pool.",x);
}
return name;
}
/**
* Creates the ObjectName for the ConnectionPoolMBean object to be registered
* @param original the ObjectName for the DataSource
* @return the ObjectName for the ConnectionPoolMBean
* @throws MalformedObjectNameException Invalid object name
*/
public ObjectName createObjectName(ObjectName original) throws MalformedObjectNameException {
String domain = ConnectionPool.POOL_JMX_DOMAIN;
Hashtable<String,String> properties = original.getKeyPropertyList();
String origDomain = original.getDomain();
properties.put("type", "ConnectionPool");
properties.put("class", this.getClass().getName());
if (original.getKeyProperty("path")!=null || properties.get("context")!=null) {
//this ensures that if the registration came from tomcat, we're not losing
//the unique domain, but putting that into as an engine attribute
properties.put("engine", origDomain);
}
ObjectName name = new ObjectName(domain,properties);
return name;
}
/**
* Registers the ConnectionPoolMBean under a unique name based on the ObjectName for the DataSource
*/
protected void registerJmx() {
if (pool.getJmxPool()!=null) {
JmxUtil.registerJmx(oname, null, pool.getJmxPool());
}
}
/**
*
*/
protected void unregisterJmx() {
JmxUtil.unregisterJmx(oname);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.SQLException;
/**
* A DisposableConnectionFacade object is the top most interceptor that wraps an
* object of type {@link PooledConnection}. The DisposableConnectionFacade intercepts
* two methods:
* <ul>
* <li>{@link java.sql.Connection#close()} - returns the connection to the
* pool then breaks the link between cutoff and the next interceptor.
* May be called multiple times.</li>
* <li>{@link java.lang.Object#toString()} - returns a custom string for this
* object</li>
* </ul>
* By default method comparisons is done on a String reference level, unless the
* {@link PoolConfiguration#setUseEquals(boolean)} has been called with a
* <code>true</code> argument.
*/
public class DisposableConnectionFacade extends JdbcInterceptor {
protected DisposableConnectionFacade(JdbcInterceptor interceptor) {
setUseEquals(interceptor.isUseEquals());
setNext(interceptor);
}
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
@Override
public boolean equals(Object obj) {
return this==obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (compare(EQUALS_VAL, method)) {
return Boolean.valueOf(
this.equals(Proxy.getInvocationHandler(args[0])));
} else if (compare(HASHCODE_VAL, method)) {
return Integer.valueOf(this.hashCode());
} else if (getNext()==null) {
if (compare(ISCLOSED_VAL, method)) {
return Boolean.TRUE;
}
else if (compare(CLOSE_VAL, method)) {
return null;
}
else if (compare(ISVALID_VAL, method)) {
return Boolean.FALSE;
}
}
try {
return super.invoke(proxy, method, args);
} catch (NullPointerException e) {
if (getNext() == null) {
if (compare(TOSTRING_VAL, method)) {
return "DisposableConnectionFacade[null]";
}
throw new SQLException(
"PooledConnection has already been closed.");
}
throw e;
} finally {
if (compare(CLOSE_VAL, method)) {
setNext(null);
}
}
}
}

View File

@@ -0,0 +1,239 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.tomcat.jdbc.pool.PoolProperties.InterceptorProperty;
/**
* Abstract class that is to be extended for implementations of interceptors.
* Everytime an operation is called on the {@link java.sql.Connection} object the
* {@link #invoke(Object, Method, Object[])} method on the interceptor will be called.
* Interceptors are useful to change or improve behavior of the connection pool.<br>
* Interceptors can receive a set of properties. Each sub class is responsible for parsing the properties during runtime when they
* are needed or simply override the {@link #setProperties(Map)} method.
* Properties arrive in a key-value pair of Strings as they were received through the configuration.
* This method is called once per cached connection object when the object is first configured.
*
* @version 1.0
*/
public abstract class JdbcInterceptor implements InvocationHandler {
/**
* {@link java.sql.Connection#close()} method name
*/
public static final String CLOSE_VAL = "close";
/**
* {@link Object#toString()} method name
*/
public static final String TOSTRING_VAL = "toString";
/**
* {@link java.sql.Connection#isClosed()} method name
*/
public static final String ISCLOSED_VAL = "isClosed";
/**
* {@link javax.sql.PooledConnection#getConnection()} method name
*/
public static final String GETCONNECTION_VAL = "getConnection";
/**
* {@link java.sql.Wrapper#unwrap(Class)} method name
*/
public static final String UNWRAP_VAL = "unwrap";
/**
* {@link java.sql.Wrapper#isWrapperFor(Class)} method name
*/
public static final String ISWRAPPERFOR_VAL = "isWrapperFor";
/**
* {@link java.sql.Connection#isValid(int)} method name
*/
public static final String ISVALID_VAL = "isValid";
/**
* {@link java.lang.Object#equals(Object)}
*/
public static final String EQUALS_VAL = "equals";
/**
* {@link java.lang.Object#hashCode()}
*/
public static final String HASHCODE_VAL = "hashCode";
/**
* Properties for this interceptor.
*/
protected Map<String,InterceptorProperty> properties = null;
/**
* The next interceptor in the chain
*/
private volatile JdbcInterceptor next = null;
/**
* Property that decides how we do string comparison, default is to use
* {@link String#equals(Object)}. If set to <code>false</code> then the
* equality operator (==) is used.
*/
private boolean useEquals = true;
/**
* Public constructor for instantiation through reflection
*/
public JdbcInterceptor() {
// NOOP
}
/**
* Gets invoked each time an operation on {@link java.sql.Connection} is invoked.
* {@inheritDoc}
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (getNext()!=null) return getNext().invoke(proxy,method,args);
else throw new NullPointerException();
}
/**
* Returns the next interceptor in the chain
* @return the next interceptor in the chain
*/
public JdbcInterceptor getNext() {
return next;
}
/**
* configures the next interceptor in the chain
* @param next The next chain item
*/
public void setNext(JdbcInterceptor next) {
this.next = next;
}
/**
* Performs a string comparison, using references unless the useEquals property is set to true.
* @param name1 The first name
* @param name2 The second name
* @return true if name1 is equal to name2 based on {@link #useEquals}
*/
public boolean compare(String name1, String name2) {
if (isUseEquals()) {
return name1.equals(name2);
} else {
return name1==name2;
}
}
/**
* Compares a method name (String) to a method (Method)
* {@link #compare(String,String)}
* Uses reference comparison unless the useEquals property is set to true
* @param methodName The method name
* @param method The method
* @return <code>true</code> if the name matches
*/
public boolean compare(String methodName, Method method) {
return compare(methodName, method.getName());
}
/**
* Gets called each time the connection is borrowed from the pool
* This means that if an interceptor holds a reference to the connection
* the interceptor can be reused for another connection.
* <br>
* This method may be called with null as both arguments when we are closing down the connection.
* @param parent - the connection pool owning the connection
* @param con - the pooled connection
*/
public abstract void reset(ConnectionPool parent, PooledConnection con);
/**
* Called when {@link java.sql.Connection#close()} is called on the underlying connection.
* This is to notify the interceptors, that the physical connection has been released.
* Implementation of this method should be thought through with care, as no actions should trigger an exception.
* @param parent - the connection pool that this connection belongs to
* @param con - the pooled connection that holds this connection
* @param finalizing - if this connection is finalizing. True means that the pooled connection will not reconnect the underlying connection
*/
public void disconnected(ConnectionPool parent, PooledConnection con, boolean finalizing) {
}
/**
* Returns the properties configured for this interceptor
* @return the configured properties for this interceptor
*/
public Map<String,InterceptorProperty> getProperties() {
return properties;
}
/**
* Called during the creation of an interceptor
* The properties can be set during the configuration of an interceptor
* Override this method to perform type casts between string values and object properties
* @param properties The properties
*/
public void setProperties(Map<String,InterceptorProperty> properties) {
this.properties = properties;
final String useEquals = "useEquals";
InterceptorProperty p = properties.get(useEquals);
if (p!=null) {
setUseEquals(Boolean.parseBoolean(p.getValue()));
}
}
/**
* @return true if the compare method uses the Object.equals(Object) method
* false if comparison is done on a reference level
*/
public boolean isUseEquals() {
return useEquals;
}
/**
* Set to true if string comparisons (for the {@link #compare(String, Method)} and {@link #compare(String, String)} methods) should use the Object.equals(Object) method
* The default is false
* @param useEquals <code>true</code> if equals will be used for comparisons
*/
public void setUseEquals(boolean useEquals) {
this.useEquals = useEquals;
}
/**
* This method is invoked by a connection pool when the pool is closed.
* Interceptor classes can override this method if they keep static
* variables or other tracking means around.
* <b>This method is only invoked on a single instance of the interceptor, and not on every instance created.</b>
* @param pool - the pool that is being closed.
*/
public void poolClosed(ConnectionPool pool) {
// NOOP
}
/**
* This method is invoked by a connection pool when the pool is first started up, usually when the first connection is requested.
* Interceptor classes can override this method if they keep static
* variables or other tracking means around.
* <b>This method is only invoked on a single instance of the interceptor, and not on every instance created.</b>
* @param pool - the pool that is being closed.
*/
public void poolStarted(ConnectionPool pool) {
// NOOP
}
}

View File

@@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
import java.sql.SQLException;
public class PoolExhaustedException extends SQLException {
private static final long serialVersionUID = 3501536931777262475L;
public PoolExhaustedException() {
}
public PoolExhaustedException(String reason) {
super(reason);
}
public PoolExhaustedException(Throwable cause) {
super(cause);
}
public PoolExhaustedException(String reason, String SQLState) {
super(reason, SQLState);
}
public PoolExhaustedException(String reason, Throwable cause) {
super(reason, cause);
}
public PoolExhaustedException(String reason, String SQLState, int vendorCode) {
super(reason, SQLState, vendorCode);
}
public PoolExhaustedException(String reason, String sqlState, Throwable cause) {
super(reason, sqlState, cause);
}
public PoolExhaustedException(String reason, String sqlState, int vendorCode, Throwable cause) {
super(reason, sqlState, vendorCode, cause);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
import java.util.Properties;
public class PoolUtilities {
public static final String PROP_USER = "user";
public static final String PROP_PASSWORD = "password";
public static Properties clone(Properties p) {
Properties c = new Properties();
c.putAll(p);
return c;
}
public static Properties cloneWithoutPassword(Properties p) {
Properties result = clone(p);
result.remove(PROP_PASSWORD);
return result;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
import java.sql.SQLException;
public interface PooledConnectionMBean {
// PooledConnection
public long getConnectionVersion();
public boolean isInitialized();
public boolean isMaxAgeExpired();
public boolean isSuspect();
public long getTimestamp();
public boolean isDiscarded();
public long getLastValidated();
public long getLastConnected();
public boolean isReleased();
// java.sql.Connection
public void clearWarnings();
public boolean isClosed() throws SQLException;
public boolean getAutoCommit() throws SQLException;
public String getCatalog() throws SQLException;
public int getHoldability() throws SQLException;
public boolean isReadOnly() throws SQLException;
public String getSchema() throws SQLException;
public int getTransactionIsolation() throws SQLException;
}

View File

@@ -0,0 +1,156 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;
import javax.sql.XAConnection;
/**
* A ProxyConnection object is the bottom most interceptor that wraps an object of type
* {@link PooledConnection}. The ProxyConnection intercepts three methods:
* <ul>
* <li>{@link java.sql.Connection#close()} - returns the connection to the pool. May be called multiple times.</li>
* <li>{@link java.lang.Object#toString()} - returns a custom string for this object</li>
* <li>{@link javax.sql.PooledConnection#getConnection()} - returns the underlying connection</li>
* </ul>
* By default method comparisons is done on a String reference level, unless the {@link PoolConfiguration#setUseEquals(boolean)} has been called
* with a <code>true</code> argument.
*/
public class ProxyConnection extends JdbcInterceptor {
protected PooledConnection connection = null;
protected ConnectionPool pool = null;
public PooledConnection getConnection() {
return connection;
}
public void setConnection(PooledConnection connection) {
this.connection = connection;
}
public ConnectionPool getPool() {
return pool;
}
public void setPool(ConnectionPool pool) {
this.pool = pool;
}
protected ProxyConnection(ConnectionPool parent, PooledConnection con,
boolean useEquals) {
pool = parent;
connection = con;
setUseEquals(useEquals);
}
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
this.pool = parent;
this.connection = con;
}
public boolean isWrapperFor(Class<?> iface) {
if (iface == XAConnection.class && connection.getXAConnection()!=null) {
return true;
} else {
return (iface.isInstance(connection.getConnection()));
}
}
public Object unwrap(Class<?> iface) throws SQLException {
if (iface == PooledConnection.class) {
return connection;
}else if (iface == XAConnection.class) {
return connection.getXAConnection();
} else if (isWrapperFor(iface)) {
return connection.getConnection();
} else {
throw new SQLException("Not a wrapper of "+iface.getName());
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (compare(ISCLOSED_VAL,method)) {
return Boolean.valueOf(isClosed());
}
if (compare(CLOSE_VAL,method)) {
if (connection==null) return null; //noop for already closed.
PooledConnection poolc = this.connection;
this.connection = null;
pool.returnConnection(poolc);
return null;
} else if (compare(TOSTRING_VAL,method)) {
return this.toString();
} else if (compare(GETCONNECTION_VAL,method) && connection!=null) {
return connection.getConnection();
} else if (method.getDeclaringClass().isAssignableFrom(XAConnection.class)) {
try {
return method.invoke(connection.getXAConnection(),args);
}catch (Throwable t) {
if (t instanceof InvocationTargetException) {
throw t.getCause() != null ? t.getCause() : t;
} else {
throw t;
}
}
}
if (isClosed()) throw new SQLException("Connection has already been closed.");
if (compare(UNWRAP_VAL,method)) {
return unwrap((Class<?>)args[0]);
} else if (compare(ISWRAPPERFOR_VAL,method)) {
return Boolean.valueOf(this.isWrapperFor((Class<?>)args[0]));
}
try {
PooledConnection poolc = connection;
if (poolc!=null) {
return method.invoke(poolc.getConnection(),args);
} else {
throw new SQLException("Connection has already been closed.");
}
}catch (Throwable t) {
if (t instanceof InvocationTargetException) {
throw t.getCause() != null ? t.getCause() : t;
} else {
throw t;
}
}
}
public boolean isClosed() {
return connection==null || connection.isDiscarded();
}
public PooledConnection getDelegateConnection() {
return connection;
}
public ConnectionPool getParentPool() {
return pool;
}
@Override
public String toString() {
return "ProxyConnection["+(connection!=null?connection.toString():"null")+"]";
}
}

View File

@@ -0,0 +1,153 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.interceptor.AbstractCreateStatementInterceptor;
public class StatementFacade extends AbstractCreateStatementInterceptor {
private static final Log logger = LogFactory.getLog(StatementFacade.class);
protected StatementFacade(JdbcInterceptor interceptor) {
setUseEquals(interceptor.isUseEquals());
setNext(interceptor);
}
@Override
public void closeInvoked() {
// nothing to do
}
/**
* Creates a statement interceptor to monitor query response times
*/
@Override
public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
try {
String name = method.getName();
Constructor<?> constructor = null;
String sql = null;
if (compare(CREATE_STATEMENT, name)) {
// createStatement
constructor = getConstructor(CREATE_STATEMENT_IDX, Statement.class);
} else if (compare(PREPARE_STATEMENT, name)) {
// prepareStatement
constructor = getConstructor(PREPARE_STATEMENT_IDX, PreparedStatement.class);
sql = (String)args[0];
} else if (compare(PREPARE_CALL, name)) {
// prepareCall
constructor = getConstructor(PREPARE_CALL_IDX, CallableStatement.class);
sql = (String)args[0];
} else {
// do nothing
return statement;
}
return constructor.newInstance(new Object[] { new StatementProxy(statement,sql) });
} catch (Exception x) {
logger.warn("Unable to create statement proxy.", x);
}
return statement;
}
/**
* Class to measure query execute time.
*/
protected class StatementProxy implements InvocationHandler {
protected boolean closed = false;
protected Object delegate;
protected final String query;
public StatementProxy(Object parent, String query) {
this.delegate = parent;
this.query = query;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (compare(TOSTRING_VAL,method)) {
return toString();
}
if (compare(EQUALS_VAL, method)) {
return Boolean.valueOf(
this.equals(Proxy.getInvocationHandler(args[0])));
}
if (compare(HASHCODE_VAL, method)) {
return Integer.valueOf(this.hashCode());
}
if (compare(CLOSE_VAL, method)) {
if (delegate == null) return null;
}
if (compare(ISCLOSED_VAL, method)) {
if (delegate == null) return Boolean.TRUE;
}
if (delegate == null) throw new SQLException("Statement closed.");
Object result = null;
try {
//invoke next
result = method.invoke(delegate,args);
} catch (Throwable t) {
if (t instanceof InvocationTargetException && t.getCause() != null) {
throw t.getCause();
} else {
throw t;
}
}
//perform close cleanup
if (compare(CLOSE_VAL, method)) {
delegate = null;
}
return result;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
@Override
public boolean equals(Object obj) {
return this==obj;
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer(StatementProxy.class.getName());
buf.append("[Proxy=");
buf.append(hashCode());
buf.append("; Query=");
buf.append(query);
buf.append("; Delegate=");
buf.append(delegate);
buf.append("]");
return buf.toString();
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;
/**
* Interceptor that traps any unhandled exception types and throws an exception that has been declared by the method
* called, or throw an SQLException if it is declared.
* If the caught exception is not declared, and the method doesn't throw SQLException, then this interceptor will
* throw a RuntimeException
*
*/
public class TrapException extends JdbcInterceptor {
public TrapException() {
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return super.invoke(proxy, method, args);
}catch (Exception t) {
Throwable exception = t;
if (t instanceof InvocationTargetException && t.getCause() != null) {
exception = t.getCause();
if (exception instanceof Error) {
throw exception;
}
}
Class<?> exceptionClass = exception.getClass();
if (!isDeclaredException(method, exceptionClass)) {
if (isDeclaredException(method,SQLException.class)) {
SQLException sqlx = new SQLException("Uncaught underlying exception.");
sqlx.initCause(exception);
exception = sqlx;
} else {
RuntimeException rx = new RuntimeException("Uncaught underlying exception.");
rx.initCause(exception);
exception = rx;
}
}
throw exception;
}
}
public boolean isDeclaredException(Method m, Class<?> clazz) {
for (Class<?> cl : m.getExceptionTypes()) {
if (cl.equals(clazz) || cl.isAssignableFrom(clazz)) return true;
}
return false;
}
/**
* no-op for this interceptor. no state is stored.
*/
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
// NOOP
}
}

View File

@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
import java.sql.Connection;
/**
* Interface to be implemented by custom validator classes.
*
* @author mpassell
*/
public interface Validator {
/**
* Validate a connection and return a boolean to indicate if it's valid.
*
* @param connection the Connection object to test
* @param validateAction the action used. One of {@link PooledConnection#VALIDATE_BORROW},
* {@link PooledConnection#VALIDATE_IDLE}, {@link PooledConnection#VALIDATE_INIT} or
* {@link PooledConnection#VALIDATE_RETURN}
* @return true if the connection is valid
*/
public boolean validate(Connection connection, int validateAction);
}

View File

@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool;
public class XADataSource extends DataSource implements javax.sql.XADataSource {
/**
* Constructor for reflection only. A default set of pool properties will be created.
*/
public XADataSource() {
super();
}
/**
* Constructs a DataSource object wrapping a connection
* @param poolProperties The pool configuration
*/
public XADataSource(PoolConfiguration poolProperties) {
super(poolProperties);
}
}

View File

@@ -0,0 +1,166 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.JdbcInterceptor;
import org.apache.tomcat.jdbc.pool.PooledConnection;
/**
* Abstraction interceptor. This component intercepts all calls to create some type of SQL statement.
* By extending this class, one can intercept queries and update statements by overriding the {@link #createStatement(Object, Method, Object[], Object, long)}
* method.
* @version 1.0
*/
public abstract class AbstractCreateStatementInterceptor extends JdbcInterceptor {
protected static final String CREATE_STATEMENT = "createStatement";
protected static final int CREATE_STATEMENT_IDX = 0;
protected static final String PREPARE_STATEMENT = "prepareStatement";
protected static final int PREPARE_STATEMENT_IDX = 1;
protected static final String PREPARE_CALL = "prepareCall";
protected static final int PREPARE_CALL_IDX = 2;
protected static final String[] STATEMENT_TYPES = {CREATE_STATEMENT, PREPARE_STATEMENT, PREPARE_CALL};
protected static final int STATEMENT_TYPE_COUNT = STATEMENT_TYPES.length;
protected static final String EXECUTE = "execute";
protected static final String EXECUTE_QUERY = "executeQuery";
protected static final String EXECUTE_UPDATE = "executeUpdate";
protected static final String EXECUTE_BATCH = "executeBatch";
protected static final String[] EXECUTE_TYPES = {EXECUTE, EXECUTE_QUERY, EXECUTE_UPDATE, EXECUTE_BATCH};
/**
* the constructors that are used to create statement proxies
*/
protected static final Constructor<?>[] constructors =
new Constructor[AbstractCreateStatementInterceptor.STATEMENT_TYPE_COUNT];
public AbstractCreateStatementInterceptor() {
super();
}
/**
* {@inheritDoc}
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (compare(CLOSE_VAL,method)) {
closeInvoked();
return super.invoke(proxy, method, args);
} else {
boolean process = false;
process = isStatement(method, process);
if (process) {
long start = System.currentTimeMillis();
Object statement = super.invoke(proxy,method,args);
long delta = System.currentTimeMillis() - start;
return createStatement(proxy,method,args,statement, delta);
} else {
return super.invoke(proxy,method,args);
}
}
}
/**
* Creates a constructor for a proxy class, if one doesn't already exist
*
* @param idx
* - the index of the constructor
* @param clazz
* - the interface that the proxy will implement
* @return - returns a constructor used to create new instances
* @throws NoSuchMethodException Constructor not found
*/
protected Constructor<?> getConstructor(int idx, Class<?> clazz) throws NoSuchMethodException {
if (constructors[idx] == null) {
Class<?> proxyClass = Proxy.getProxyClass(AbstractCreateStatementInterceptor.class.getClassLoader(),
new Class[] { clazz });
constructors[idx] = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
}
return constructors[idx];
}
/**
* This method will be invoked after a successful statement creation. This method can choose to return a wrapper
* around the statement or return the statement itself.
* If this method returns a wrapper then it should return a wrapper object that implements one of the following interfaces.
* {@link java.sql.Statement}, {@link java.sql.PreparedStatement} or {@link java.sql.CallableStatement}
* @param proxy the actual proxy object
* @param method the method that was called. It will be one of the methods defined in {@link #STATEMENT_TYPES}
* @param args the arguments to the method
* @param statement the statement that the underlying connection created
* @param time Elapsed time
* @return a {@link java.sql.Statement} object
*/
public abstract Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time);
/**
* Method invoked when the operation {@link java.sql.Connection#close()} is invoked.
*/
public abstract void closeInvoked();
/**
* Returns true if the method that is being invoked matches one of the statement types.
*
* @param method the method being invoked on the proxy
* @param process boolean result used for recursion
* @return returns true if the method name matched
*/
protected boolean isStatement(Method method, boolean process){
return process(STATEMENT_TYPES, method, process);
}
/**
* Returns true if the method that is being invoked matches one of the execute types.
*
* @param method the method being invoked on the proxy
* @param process boolean result used for recursion
* @return returns true if the method name matched
*/
protected boolean isExecute(Method method, boolean process){
return process(EXECUTE_TYPES, method, process);
}
/*
* Returns true if the method that is being invoked matches one of the method names passed in
* @param names list of method names that we want to intercept
* @param method the method being invoked on the proxy
* @param process boolean result used for recursion
* @return returns true if the method name matched
*/
protected boolean process(String[] names, Method method, boolean process) {
final String name = method.getName();
for (int i=0; (!process) && i<names.length; i++) {
process = compare(names[i],name);
}
return process;
}
/**
* no-op for this interceptor. no state is stored.
*/
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
// NOOP
}
}

View File

@@ -0,0 +1,242 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.JdbcInterceptor;
/**
* Abstract class that wraps statements and intercepts query executions.
*
*/
public abstract class AbstractQueryReport extends AbstractCreateStatementInterceptor {
//logger
private static final Log log = LogFactory.getLog(AbstractQueryReport.class);
/**
* The threshold in milliseconds. If the query is faster than this, we don't measure it
*/
protected long threshold = 1000; //don't report queries less than this
public AbstractQueryReport() {
super();
}
/**
* Invoked when prepareStatement has been called and completed.
* @param sql - the string used to prepare the statement with
* @param time - the time it took to invoke prepare
*/
protected abstract void prepareStatement(String sql, long time);
/**
* Invoked when prepareCall has been called and completed.
* @param query - the string used to prepare the statement with
* @param time - the time it took to invoke prepare
*/
protected abstract void prepareCall(String query, long time);
/**
* Invoked when a query execution, a call to execute/executeQuery or executeBatch failed.
* @param query the query that was executed and failed
* @param args the arguments to the execution
* @param name the name of the method used to execute {@link AbstractCreateStatementInterceptor#isExecute(Method, boolean)}
* @param start the time the query execution started
* @param t the exception that happened
* @return - the SQL that was executed or the string &quot;batch&quot; if it was a batch execution
*/
protected String reportFailedQuery(String query, Object[] args, final String name, long start, Throwable t) {
//extract the query string
String sql = (query==null && args!=null && args.length>0)?(String)args[0]:query;
//if we do batch execution, then we name the query 'batch'
if (sql==null && compare(EXECUTE_BATCH,name)) {
sql = "batch";
}
return sql;
}
/**
* Invoked when a query execution, a call to execute/executeQuery or executeBatch succeeded and was within the timing threshold
* @param query the query that was executed and failed
* @param args the arguments to the execution
* @param name the name of the method used to execute {@link AbstractCreateStatementInterceptor#isExecute(Method, boolean)}
* @param start the time the query execution started
* @param delta the time the execution took
* @return - the SQL that was executed or the string &quot;batch&quot; if it was a batch execution
*/
protected String reportQuery(String query, Object[] args, final String name, long start, long delta) {
//extract the query string
String sql = (query==null && args!=null && args.length>0)?(String)args[0]:query;
//if we do batch execution, then we name the query 'batch'
if (sql==null && compare(EXECUTE_BATCH,name)) {
sql = "batch";
}
return sql;
}
/**
* Invoked when a query execution, a call to execute/executeQuery or executeBatch succeeded and was exceeded the timing threshold
* @param query the query that was executed and failed
* @param args the arguments to the execution
* @param name the name of the method used to execute {@link AbstractCreateStatementInterceptor#isExecute(Method, boolean)}
* @param start the time the query execution started
* @param delta the time the execution took
* @return - the SQL that was executed or the string &quot;batch&quot; if it was a batch execution
*/
protected String reportSlowQuery(String query, Object[] args, final String name, long start, long delta) {
//extract the query string
String sql = (query==null && args!=null && args.length>0)?(String)args[0]:query;
//if we do batch execution, then we name the query 'batch'
if (sql==null && compare(EXECUTE_BATCH,name)) {
sql = "batch";
}
return sql;
}
/**
* returns the query measure threshold.
* This value is in milliseconds. If the query is faster than this threshold than it wont be accounted for
* @return the threshold in milliseconds
*/
public long getThreshold() {
return threshold;
}
/**
* Sets the query measurement threshold. The value is in milliseconds.
* If the query goes faster than this threshold it will not be recorded.
* @param threshold set to -1 to record every query. Value is in milliseconds.
*/
public void setThreshold(long threshold) {
this.threshold = threshold;
}
/**
* Creates a statement interceptor to monitor query response times
*/
@Override
public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
try {
Object result = null;
String name = method.getName();
String sql = null;
Constructor<?> constructor = null;
if (compare(CREATE_STATEMENT,name)) {
//createStatement
constructor = getConstructor(CREATE_STATEMENT_IDX,Statement.class);
}else if (compare(PREPARE_STATEMENT,name)) {
//prepareStatement
sql = (String)args[0];
constructor = getConstructor(PREPARE_STATEMENT_IDX,PreparedStatement.class);
if (sql!=null) {
prepareStatement(sql, time);
}
}else if (compare(PREPARE_CALL,name)) {
//prepareCall
sql = (String)args[0];
constructor = getConstructor(PREPARE_CALL_IDX,CallableStatement.class);
prepareCall(sql,time);
}else {
//do nothing, might be a future unsupported method
//so we better bail out and let the system continue
return statement;
}
result = constructor.newInstance(new Object[] { new StatementProxy(statement,sql) });
return result;
}catch (Exception x) {
log.warn("Unable to create statement proxy for slow query report.",x);
}
return statement;
}
/**
* Class to measure query execute time
*
*/
protected class StatementProxy implements InvocationHandler {
protected boolean closed = false;
protected Object delegate;
protected final String query;
public StatementProxy(Object parent, String query) {
this.delegate = parent;
this.query = query;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//get the name of the method for comparison
final String name = method.getName();
//was close invoked?
boolean close = compare(JdbcInterceptor.CLOSE_VAL,name);
//allow close to be called multiple times
if (close && closed) return null;
//are we calling isClosed?
if (compare(JdbcInterceptor.ISCLOSED_VAL,name)) return Boolean.valueOf(closed);
//if we are calling anything else, bail out
if (closed) throw new SQLException("Statement closed.");
boolean process = false;
//check to see if we are about to execute a query
process = isExecute( method, process);
//if we are executing, get the current time
long start = (process)?System.currentTimeMillis():0;
Object result = null;
try {
//execute the query
result = method.invoke(delegate,args);
}catch (Throwable t) {
reportFailedQuery(query,args,name,start,t);
if (t instanceof InvocationTargetException
&& t.getCause() != null) {
throw t.getCause();
} else {
throw t;
}
}
//measure the time
long delta = (process)?(System.currentTimeMillis()-start):Long.MIN_VALUE;
//see if we meet the requirements to measure
if (delta>threshold) {
try {
//report the slow query
reportSlowQuery(query, args, name, start, delta);
}catch (Exception t) {
if (log.isWarnEnabled()) log.warn("Unable to process slow query",t);
}
} else if (process) {
reportQuery(query, args, name, start, delta);
}
//perform close cleanup
if (close) {
closed=true;
delegate = null;
}
return result;
}
}
}

View File

@@ -0,0 +1,164 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
import java.lang.reflect.Method;
import java.sql.SQLException;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.DataSourceFactory;
import org.apache.tomcat.jdbc.pool.JdbcInterceptor;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.apache.tomcat.jdbc.pool.PooledConnection;
/**
* Interceptor that keep track of connection state to avoid roundtrips to the database.
* The {@link org.apache.tomcat.jdbc.pool.ConnectionPool} is optimized to do as little work as possible.
* The pool itself doesn't remember settings like {@link java.sql.Connection#setAutoCommit(boolean)},
* {@link java.sql.Connection#setReadOnly(boolean)}, {@link java.sql.Connection#setCatalog(String)} or
* {@link java.sql.Connection#setTransactionIsolation(int)}. It relies on the application to remember how and when
* these settings have been applied.
* In the cases where the application code doesn't know or want to keep track of the state, this interceptor helps cache the
* state, and it also avoids roundtrips to the database asking for it.
*
*/
public class ConnectionState extends JdbcInterceptor {
private static final Log log = LogFactory.getLog(ConnectionState.class);
protected final String[] readState = {"getAutoCommit","getTransactionIsolation","isReadOnly","getCatalog"};
protected final String[] writeState = {"setAutoCommit","setTransactionIsolation","setReadOnly","setCatalog"};
protected Boolean autoCommit = null;
protected Integer transactionIsolation = null;
protected Boolean readOnly = null;
protected String catalog = null;
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
if (parent==null || con==null) {
//we are resetting, reset our defaults
autoCommit = null;
transactionIsolation = null;
readOnly = null;
catalog = null;
return;
}
PoolConfiguration poolProperties = parent.getPoolProperties();
if (poolProperties.getDefaultTransactionIsolation()!=DataSourceFactory.UNKNOWN_TRANSACTIONISOLATION) {
try {
if (transactionIsolation==null || transactionIsolation.intValue()!=poolProperties.getDefaultTransactionIsolation()) {
con.getConnection().setTransactionIsolation(poolProperties.getDefaultTransactionIsolation());
transactionIsolation = Integer.valueOf(poolProperties.getDefaultTransactionIsolation());
}
}catch (SQLException x) {
transactionIsolation = null;
log.error("Unable to reset transaction isolation state to connection.",x);
}
}
if (poolProperties.getDefaultReadOnly()!=null) {
try {
if (readOnly==null || readOnly.booleanValue()!=poolProperties.getDefaultReadOnly().booleanValue()) {
con.getConnection().setReadOnly(poolProperties.getDefaultReadOnly().booleanValue());
readOnly = poolProperties.getDefaultReadOnly();
}
}catch (SQLException x) {
readOnly = null;
log.error("Unable to reset readonly state to connection.",x);
}
}
if (poolProperties.getDefaultAutoCommit()!=null) {
try {
if (autoCommit==null || autoCommit.booleanValue()!=poolProperties.getDefaultAutoCommit().booleanValue()) {
con.getConnection().setAutoCommit(poolProperties.getDefaultAutoCommit().booleanValue());
autoCommit = poolProperties.getDefaultAutoCommit();
}
}catch (SQLException x) {
autoCommit = null;
log.error("Unable to reset autocommit state to connection.",x);
}
}
if (poolProperties.getDefaultCatalog()!=null) {
try {
if (catalog==null || (!catalog.equals(poolProperties.getDefaultCatalog()))) {
con.getConnection().setCatalog(poolProperties.getDefaultCatalog());
catalog = poolProperties.getDefaultCatalog();
}
}catch (SQLException x) {
catalog = null;
log.error("Unable to reset default catalog state to connection.",x);
}
}
}
@Override
public void disconnected(ConnectionPool parent, PooledConnection con, boolean finalizing) {
//we are resetting, reset our defaults
autoCommit = null;
transactionIsolation = null;
readOnly = null;
catalog = null;
super.disconnected(parent, con, finalizing);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
boolean read = false;
int index = -1;
for (int i=0; (!read) && i<readState.length; i++) {
read = compare(name,readState[i]);
if (read) index = i;
}
boolean write = false;
for (int i=0; (!write) && (!read) && i<writeState.length; i++) {
write = compare(name,writeState[i]);
if (write) index = i;
}
Object result = null;
if (read) {
switch (index) {
case 0:{result = autoCommit; break;}
case 1:{result = transactionIsolation; break;}
case 2:{result = readOnly; break;}
case 3:{result = catalog; break;}
default: // NOOP
}
//return cached result, if we have it
if (result!=null) return result;
}
result = super.invoke(proxy, method, args);
if (read || write) {
switch (index) {
case 0:{autoCommit = (Boolean) (read?result:args[0]); break;}
case 1:{transactionIsolation = (Integer)(read?result:args[0]); break;}
case 2:{readOnly = (Boolean)(read?result:args[0]); break;}
case 3:{catalog = (String)(read?result:args[0]); break;}
}
}
return result;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.PoolProperties.InterceptorProperty;
public class QueryTimeoutInterceptor extends AbstractCreateStatementInterceptor {
private static Log log = LogFactory.getLog(QueryTimeoutInterceptor.class);
int timeout = 1;
@Override
public void setProperties(Map<String,InterceptorProperty> properties) {
super.setProperties(properties);
InterceptorProperty p = properties.get("queryTimeout");
if (p!=null) timeout = p.getValueAsInt(timeout);
}
@Override
public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
if (statement instanceof Statement && timeout > 0) {
Statement s = (Statement)statement;
try {
s.setQueryTimeout(timeout);
}catch (SQLException x) {
log.warn("[QueryTimeoutInterceptor] Unable to set query timeout:"+x.getMessage(),x);
}
}
return statement;
}
@Override
public void closeInvoked() {
}
}

View File

@@ -0,0 +1,107 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
import java.lang.reflect.Method;
import javax.management.ObjectName;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.PooledConnection;
import org.apache.tomcat.jdbc.pool.jmx.JmxUtil;
/**
* Class that resets the abandoned timer on any activity on the
* Connection or any successful query executions.
* This interceptor is useful for when you have a {@link org.apache.tomcat.jdbc.pool.PoolConfiguration#setRemoveAbandonedTimeout(int)}
* that is fairly low, and you want to reset the abandoned time each time any operation on the connection is performed
* This is useful for batch processing programs that use connections for extensive amount of times.
*
*/
public class ResetAbandonedTimer extends AbstractQueryReport implements ResetAbandonedTimerMBean {
private PooledConnection pcon;
private ObjectName oname = null;
public ResetAbandonedTimer() {
}
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
super.reset(parent, con);
if (con == null) {
this.pcon = null;
if (oname != null) {
JmxUtil.unregisterJmx(oname);
oname = null;
}
} else {
this.pcon = con;
if (oname == null) {
String keyprop = ",JdbcInterceptor=" + getClass().getSimpleName();
oname = JmxUtil.registerJmx(pcon.getObjectName(), keyprop, this);
}
}
}
@Override
public boolean resetTimer() {
boolean result = false;
if (pcon != null) {
pcon.setTimestamp(System.currentTimeMillis());
result = true;
}
return result;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = super.invoke(proxy, method, args);
resetTimer();
return result;
}
@Override
protected void prepareCall(String query, long time) {
resetTimer();
}
@Override
protected void prepareStatement(String sql, long time) {
resetTimer();
}
@Override
public void closeInvoked() {
resetTimer();
}
@Override
protected String reportQuery(String query, Object[] args, String name,long start, long delta) {
resetTimer();
return super.reportQuery(query, args, name, start, delta);
}
@Override
protected String reportSlowQuery(String query, Object[] args, String name,long start, long delta) {
resetTimer();
return super.reportSlowQuery(query, args, name, start, delta);
}
}

View File

@@ -0,0 +1,22 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
public interface ResetAbandonedTimerMBean {
public boolean resetTimer();
}

View File

@@ -0,0 +1,497 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.PoolProperties.InterceptorProperty;
import org.apache.tomcat.jdbc.pool.PooledConnection;
/**
* Slow query report interceptor. Tracks timing of query executions.
* @version 1.0
*/
public class SlowQueryReport extends AbstractQueryReport {
//logger
private static final Log log = LogFactory.getLog(SlowQueryReport.class);
/**
* we will be keeping track of query stats on a per pool basis
*/
protected static final ConcurrentHashMap<String,ConcurrentHashMap<String,QueryStats>> perPoolStats =
new ConcurrentHashMap<>();
/**
* the queries that are used for this interceptor.
*/
protected volatile ConcurrentHashMap<String,QueryStats> queries = null;
/**
* Maximum number of queries we will be storing
*/
protected int maxQueries= 1000; //don't store more than this amount of queries
/**
* Flag to enable disable logging of slow queries
*/
protected boolean logSlow = true;
/**
* Flag to enable disable logging of failed queries
*/
protected boolean logFailed = false;
/**
* Sort QueryStats by last invocation time
*/
protected final Comparator<QueryStats> queryStatsComparator = new QueryStatsComparator();
/**
* Returns the query stats for a given pool
* @param poolname - the name of the pool we want to retrieve stats for
* @return a hash map containing statistics for 0 to maxQueries
*/
public static ConcurrentHashMap<String,QueryStats> getPoolStats(String poolname) {
return perPoolStats.get(poolname);
}
/**
* Creates a slow query report interceptor
*/
public SlowQueryReport() {
super();
}
public void setMaxQueries(int maxQueries) {
this.maxQueries = maxQueries;
}
@Override
protected String reportFailedQuery(String query, Object[] args, String name, long start, Throwable t) {
String sql = super.reportFailedQuery(query, args, name, start, t);
if (this.maxQueries > 0 ) {
long now = System.currentTimeMillis();
long delta = now - start;
QueryStats qs = this.getQueryStats(sql);
if (qs != null) {
qs.failure(delta, now);
}
if (isLogFailed() && log.isWarnEnabled()) {
log.warn("Failed Query Report SQL="+sql+"; time="+delta+" ms;");
}
}
return sql;
}
@Override
protected String reportQuery(String query, Object[] args, final String name, long start, long delta) {
String sql = super.reportQuery(query, args, name, start, delta);
if (this.maxQueries > 0 ) {
QueryStats qs = this.getQueryStats(sql);
if (qs != null) qs.add(delta, start);
}
return sql;
}
@Override
protected String reportSlowQuery(String query, Object[] args, String name, long start, long delta) {
String sql = super.reportSlowQuery(query, args, name, start, delta);
if (this.maxQueries > 0 ) {
QueryStats qs = this.getQueryStats(sql);
if (qs != null) {
qs.add(delta, start);
if (isLogSlow() && log.isWarnEnabled()) {
log.warn("Slow Query Report SQL="+sql+"; time="+delta+" ms;");
}
}
}
return sql;
}
/**
* invoked when the connection receives the close request
* Not used for now.
*/
@Override
public void closeInvoked() {
// NOOP
}
@Override
public void prepareStatement(String sql, long time) {
if (this.maxQueries > 0 ) {
QueryStats qs = getQueryStats(sql);
if (qs != null) qs.prepare(time);
}
}
@Override
public void prepareCall(String sql, long time) {
if (this.maxQueries > 0 ) {
QueryStats qs = getQueryStats(sql);
if (qs != null) qs.prepare(time);
}
}
/**
* {@inheritDoc}
*/
@Override
public void poolStarted(ConnectionPool pool) {
super.poolStarted(pool);
//see if we already created a map for this pool
queries = SlowQueryReport.perPoolStats.get(pool.getName());
if (queries==null) {
//create the map to hold our stats
//however TODO we need to improve the eviction
//selection
queries = new ConcurrentHashMap<>();
if (perPoolStats.putIfAbsent(pool.getName(), queries)!=null) {
//there already was one
queries = SlowQueryReport.perPoolStats.get(pool.getName());
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void poolClosed(ConnectionPool pool) {
perPoolStats.remove(pool.getName());
super.poolClosed(pool);
}
protected QueryStats getQueryStats(String sql) {
if (sql==null) sql = "";
ConcurrentHashMap<String,QueryStats> queries = SlowQueryReport.this.queries;
if (queries==null) {
if (log.isWarnEnabled()) log.warn("Connection has already been closed or abandoned");
return null;
}
QueryStats qs = queries.get(sql);
if (qs == null) {
qs = new QueryStats(sql);
if (queries.putIfAbsent(sql,qs)!=null) {
qs = queries.get(sql);
} else {
//we added a new element, see if we need to remove the oldest
if (queries.size() > maxQueries) {
removeOldest(queries);
}
}
}
return qs;
}
/**
* Sort QueryStats by last invocation time
* @param queries The queries map
*/
protected void removeOldest(ConcurrentHashMap<String,QueryStats> queries) {
ArrayList<QueryStats> list = new ArrayList<>(queries.values());
Collections.sort(list, queryStatsComparator);
int removeIndex = 0;
while (queries.size() > maxQueries) {
String sql = list.get(removeIndex).getQuery();
queries.remove(sql);
if (log.isDebugEnabled()) log.debug("Removing slow query, capacity reached:"+sql);
removeIndex++;
}
}
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
super.reset(parent, con);
if (parent!=null)
queries = SlowQueryReport.perPoolStats.get(parent.getName());
else
queries = null;
}
public boolean isLogSlow() {
return logSlow;
}
public void setLogSlow(boolean logSlow) {
this.logSlow = logSlow;
}
public boolean isLogFailed() {
return logFailed;
}
public void setLogFailed(boolean logFailed) {
this.logFailed = logFailed;
}
@Override
public void setProperties(Map<String, InterceptorProperty> properties) {
super.setProperties(properties);
final String threshold = "threshold";
final String maxqueries= "maxQueries";
final String logslow = "logSlow";
final String logfailed = "logFailed";
InterceptorProperty p1 = properties.get(threshold);
InterceptorProperty p2 = properties.get(maxqueries);
InterceptorProperty p3 = properties.get(logslow);
InterceptorProperty p4 = properties.get(logfailed);
if (p1!=null) {
setThreshold(Long.parseLong(p1.getValue()));
}
if (p2!=null) {
setMaxQueries(Integer.parseInt(p2.getValue()));
}
if (p3!=null) {
setLogSlow(Boolean.parseBoolean(p3.getValue()));
}
if (p4!=null) {
setLogFailed(Boolean.parseBoolean(p4.getValue()));
}
}
public static class QueryStats {
static final String[] FIELD_NAMES = new String[] {
"query",
"nrOfInvocations",
"maxInvocationTime",
"maxInvocationDate",
"minInvocationTime",
"minInvocationDate",
"totalInvocationTime",
"failures",
"prepareCount",
"prepareTime",
"lastInvocation"
};
static final String[] FIELD_DESCRIPTIONS = new String[] {
"The SQL query",
"The number of query invocations, a call to executeXXX",
"The longest time for this query in milliseconds",
"The time and date for when the longest query took place",
"The shortest time for this query in milliseconds",
"The time and date for when the shortest query took place",
"The total amount of milliseconds spent executing this query",
"The number of failures for this query",
"The number of times this query was prepared (prepareStatement/prepareCall)",
"The total number of milliseconds spent preparing this query",
"The date and time of the last invocation"
};
static final OpenType<?>[] FIELD_TYPES = new OpenType[] {
SimpleType.STRING,
SimpleType.INTEGER,
SimpleType.LONG,
SimpleType.LONG,
SimpleType.LONG,
SimpleType.LONG,
SimpleType.LONG,
SimpleType.LONG,
SimpleType.INTEGER,
SimpleType.LONG,
SimpleType.LONG
};
private final String query;
private volatile int nrOfInvocations;
private volatile long maxInvocationTime = Long.MIN_VALUE;
private volatile long maxInvocationDate;
private volatile long minInvocationTime = Long.MAX_VALUE;
private volatile long minInvocationDate;
private volatile long totalInvocationTime;
private volatile long failures;
private volatile int prepareCount;
private volatile long prepareTime;
private volatile long lastInvocation = 0;
public static String[] getFieldNames() {
return FIELD_NAMES;
}
public static String[] getFieldDescriptions() {
return FIELD_DESCRIPTIONS;
}
public static OpenType<?>[] getFieldTypes() {
return FIELD_TYPES;
}
@Override
public String toString() {
SimpleDateFormat sdf =
new SimpleDateFormat("d MMM yyyy HH:mm:ss z", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
StringBuilder buf = new StringBuilder("QueryStats[query:");
buf.append(query);
buf.append(", nrOfInvocations:");
buf.append(nrOfInvocations);
buf.append(", maxInvocationTime:");
buf.append(maxInvocationTime);
buf.append(", maxInvocationDate:");
buf.append(sdf.format(new java.util.Date(maxInvocationDate)));
buf.append(", minInvocationTime:");
buf.append(minInvocationTime);
buf.append(", minInvocationDate:");
buf.append(sdf.format(new java.util.Date(minInvocationDate)));
buf.append(", totalInvocationTime:");
buf.append(totalInvocationTime);
buf.append(", averageInvocationTime:");
buf.append((float)totalInvocationTime / (float)nrOfInvocations);
buf.append(", failures:");
buf.append(failures);
buf.append(", prepareCount:");
buf.append(prepareCount);
buf.append(", prepareTime:");
buf.append(prepareTime);
buf.append("]");
return buf.toString();
}
public CompositeDataSupport getCompositeData(final CompositeType type) throws OpenDataException{
Object[] values = new Object[] {
query,
Integer.valueOf(nrOfInvocations),
Long.valueOf(maxInvocationTime),
Long.valueOf(maxInvocationDate),
Long.valueOf(minInvocationTime),
Long.valueOf(minInvocationDate),
Long.valueOf(totalInvocationTime),
Long.valueOf(failures),
Integer.valueOf(prepareCount),
Long.valueOf(prepareTime),
Long.valueOf(lastInvocation)
};
return new CompositeDataSupport(type,FIELD_NAMES,values);
}
public QueryStats(String query) {
this.query = query;
}
public void prepare(long invocationTime) {
prepareCount++;
prepareTime+=invocationTime;
}
public void add(long invocationTime, long now) {
//not thread safe, but don't sacrifice performance for this kind of stuff
maxInvocationTime = Math.max(invocationTime, maxInvocationTime);
if (maxInvocationTime == invocationTime) {
maxInvocationDate = now;
}
minInvocationTime = Math.min(invocationTime, minInvocationTime);
if (minInvocationTime==invocationTime) {
minInvocationDate = now;
}
nrOfInvocations++;
totalInvocationTime+=invocationTime;
lastInvocation = now;
}
public void failure(long invocationTime, long now) {
add(invocationTime,now);
failures++;
}
public String getQuery() {
return query;
}
public int getNrOfInvocations() {
return nrOfInvocations;
}
public long getMaxInvocationTime() {
return maxInvocationTime;
}
public long getMaxInvocationDate() {
return maxInvocationDate;
}
public long getMinInvocationTime() {
return minInvocationTime;
}
public long getMinInvocationDate() {
return minInvocationDate;
}
public long getTotalInvocationTime() {
return totalInvocationTime;
}
@Override
public int hashCode() {
return query.hashCode();
}
@Override
public boolean equals(Object other) {
if (other instanceof QueryStats) {
QueryStats qs = (QueryStats)other;
return qs.query.equals(this.query);
}
return false;
}
public boolean isOlderThan(QueryStats other) {
return this.lastInvocation < other.lastInvocation;
}
}
/** Compare QueryStats by their lastInvocation value. QueryStats that
* have never been updated, have a lastInvocation value of {@code 0}
* which should be handled as the newest possible invocation.
*/
// Public for unit tests
public static class QueryStatsComparator implements Comparator<QueryStats> {
@Override
public int compare(QueryStats stats1, QueryStats stats2) {
return Long.compare(handleZero(stats1.lastInvocation),
handleZero(stats2.lastInvocation));
}
private static long handleZero(long value) {
return value == 0 ? Long.MAX_VALUE : value;
}
}
}

View File

@@ -0,0 +1,301 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.RuntimeOperationsException;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.PoolProperties.InterceptorProperty;
import org.apache.tomcat.jdbc.pool.PooledConnection;
import org.apache.tomcat.jdbc.pool.jmx.JmxUtil;
/**
* Publishes data to JMX and provides notifications
* when failures happen.
*
*/
public class SlowQueryReportJmx extends SlowQueryReport implements NotificationEmitter, SlowQueryReportJmxMBean{
public static final String SLOW_QUERY_NOTIFICATION = "SLOW QUERY";
public static final String FAILED_QUERY_NOTIFICATION = "FAILED QUERY";
public static final String objectNameAttribute = "objectName";
protected static volatile CompositeType SLOW_QUERY_TYPE;
private static final Log log = LogFactory.getLog(SlowQueryReportJmx.class);
protected static final ConcurrentHashMap<String,SlowQueryReportJmxMBean> mbeans =
new ConcurrentHashMap<>();
//==============================JMX STUFF========================
protected volatile NotificationBroadcasterSupport notifier = new NotificationBroadcasterSupport();
@Override
public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException {
notifier.addNotificationListener(listener, filter, handback);
}
@Override
public MBeanNotificationInfo[] getNotificationInfo() {
return notifier.getNotificationInfo();
}
@Override
public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException {
notifier.removeNotificationListener(listener);
}
@Override
public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws ListenerNotFoundException {
notifier.removeNotificationListener(listener, filter, handback);
}
//==============================JMX STUFF========================
protected String poolName = null;
protected static final AtomicLong notifySequence = new AtomicLong(0);
protected boolean notifyPool = true;
protected ConnectionPool pool = null;
protected static CompositeType getCompositeType() {
if (SLOW_QUERY_TYPE==null) {
try {
SLOW_QUERY_TYPE = new CompositeType(
SlowQueryReportJmx.class.getName(),
"Composite data type for query statistics",
QueryStats.getFieldNames(),
QueryStats.getFieldDescriptions(),
QueryStats.getFieldTypes());
}catch (OpenDataException x) {
log.warn("Unable to initialize composite data type for JMX stats and notifications.",x);
}
}
return SLOW_QUERY_TYPE;
}
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
super.reset(parent, con);
if (parent!=null) {
poolName = parent.getName();
pool = parent;
registerJmx();
}
}
@Override
public void poolClosed(ConnectionPool pool) {
this.poolName = pool.getName();
deregisterJmx();
super.poolClosed(pool);
}
@Override
public void poolStarted(ConnectionPool pool) {
this.pool = pool;
super.poolStarted(pool);
this.poolName = pool.getName();
}
@Override
protected String reportFailedQuery(String query, Object[] args, String name, long start, Throwable t) {
query = super.reportFailedQuery(query, args, name, start, t);
if (isLogFailed()) notifyJmx(query,FAILED_QUERY_NOTIFICATION);
return query;
}
protected void notifyJmx(String query, String type) {
try {
long sequence = notifySequence.incrementAndGet();
if (isNotifyPool()) {
if (this.pool!=null && this.pool.getJmxPool()!=null) {
this.pool.getJmxPool().notify(type, query);
}
} else {
if (notifier!=null) {
Notification notification =
new Notification(type,
this,
sequence,
System.currentTimeMillis(),
query);
notifier.sendNotification(notification);
}
}
} catch (RuntimeOperationsException e) {
if (log.isDebugEnabled()) {
log.debug("Unable to send failed query notification.",e);
}
}
}
@Override
protected String reportSlowQuery(String query, Object[] args, String name, long start, long delta) {
query = super.reportSlowQuery(query, args, name, start, delta);
if (isLogSlow()) notifyJmx(query,SLOW_QUERY_NOTIFICATION);
return query;
}
/**
* JMX operation - return the names of all the pools
* @return - all the names of pools that we have stored data for
*/
public String[] getPoolNames() {
Set<String> keys = perPoolStats.keySet();
return keys.toArray(new String[0]);
}
/**
* JMX operation - return the name of the pool
* @return the name of the pool, unique within the JVM
*/
public String getPoolName() {
return poolName;
}
public boolean isNotifyPool() {
return notifyPool;
}
public void setNotifyPool(boolean notifyPool) {
this.notifyPool = notifyPool;
}
/**
* JMX operation - remove all stats for this connection pool
*/
public void resetStats() {
ConcurrentHashMap<String,QueryStats> queries = perPoolStats.get(poolName);
if (queries!=null) {
Iterator<String> it = queries.keySet().iterator();
while (it.hasNext()) it.remove();
}
}
/**
* JMX operation - returns all the queries we have collected.
* @return - the slow query report as composite data.
*/
@Override
public CompositeData[] getSlowQueriesCD() throws OpenDataException {
CompositeDataSupport[] result = null;
ConcurrentHashMap<String,QueryStats> queries = perPoolStats.get(poolName);
if (queries!=null) {
Set<Map.Entry<String,QueryStats>> stats = queries.entrySet();
if (stats!=null) {
result = new CompositeDataSupport[stats.size()];
Iterator<Map.Entry<String,QueryStats>> it = stats.iterator();
int pos = 0;
while (it.hasNext()) {
Map.Entry<String,QueryStats> entry = it.next();
QueryStats qs = entry.getValue();
result[pos++] = qs.getCompositeData(getCompositeType());
}
}
}
return result;
}
protected void deregisterJmx() {
try {
if (mbeans.remove(poolName)!=null) {
ObjectName oname = getObjectName(getClass(),poolName);
JmxUtil.unregisterJmx(oname);
}
} catch (MalformedObjectNameException e) {
log.warn("Jmx deregistration failed.",e);
} catch (RuntimeOperationsException e) {
log.warn("Jmx deregistration failed.",e);
}
}
public ObjectName getObjectName(Class<?> clazz, String poolName) throws MalformedObjectNameException {
ObjectName oname;
Map<String,InterceptorProperty> properties = getProperties();
if (properties != null && properties.containsKey(objectNameAttribute)) {
oname = new ObjectName(properties.get(objectNameAttribute).getValue());
} else {
oname = new ObjectName(ConnectionPool.POOL_JMX_TYPE_PREFIX+clazz.getName()+",name=" + poolName);
}
return oname;
}
protected void registerJmx() {
try {
//only if we notify the pool itself
if (isNotifyPool()) {
} else if (getCompositeType()!=null) {
ObjectName oname = getObjectName(getClass(),poolName);
if (mbeans.putIfAbsent(poolName, this)==null) {
JmxUtil.registerJmx(oname, null, this);
}
} else {
log.warn(SlowQueryReport.class.getName()+ "- No JMX support, composite type was not found.");
}
} catch (MalformedObjectNameException e) {
log.error("Jmx registration failed, no JMX data will be exposed for the query stats.",e);
} catch (RuntimeOperationsException e) {
log.error("Jmx registration failed, no JMX data will be exposed for the query stats.",e);
}
}
@Override
public void setProperties(Map<String, InterceptorProperty> properties) {
super.setProperties(properties);
final String threshold = "notifyPool";
InterceptorProperty p1 = properties.get(threshold);
if (p1!=null) {
this.setNotifyPool(Boolean.parseBoolean(p1.getValue()));
}
}
}

View File

@@ -0,0 +1,23 @@
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.OpenDataException;
public interface SlowQueryReportJmxMBean {
public CompositeData[] getSlowQueriesCD() throws OpenDataException;
}

View File

@@ -0,0 +1,388 @@
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.management.ObjectName;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.PoolProperties.InterceptorProperty;
import org.apache.tomcat.jdbc.pool.PooledConnection;
import org.apache.tomcat.jdbc.pool.jmx.JmxUtil;
/**
* Interceptor that caches {@code PreparedStatement} and/or
* {@code CallableStatement} instances on a connection.
*/
public class StatementCache extends StatementDecoratorInterceptor implements StatementCacheMBean {
private static final Log log = LogFactory.getLog(StatementCache.class);
protected static final String[] ALL_TYPES = new String[] {PREPARE_STATEMENT,PREPARE_CALL};
protected static final String[] CALLABLE_TYPE = new String[] {PREPARE_CALL};
protected static final String[] PREPARED_TYPE = new String[] {PREPARE_STATEMENT};
protected static final String[] NO_TYPE = new String[] {};
protected static final String STATEMENT_CACHE_ATTR = StatementCache.class.getName() + ".cache";
/*begin properties for the statement cache*/
private boolean cachePrepared = true;
private boolean cacheCallable = false;
private int maxCacheSize = 50;
private PooledConnection pcon;
private String[] types;
private ObjectName oname = null;
@Override
public boolean isCachePrepared() {
return cachePrepared;
}
@Override
public boolean isCacheCallable() {
return cacheCallable;
}
@Override
public int getMaxCacheSize() {
return maxCacheSize;
}
public String[] getTypes() {
return types;
}
@Override
public AtomicInteger getCacheSize() {
return cacheSize;
}
@Override
public void setProperties(Map<String, InterceptorProperty> properties) {
super.setProperties(properties);
InterceptorProperty p = properties.get("prepared");
if (p!=null) cachePrepared = p.getValueAsBoolean(cachePrepared);
p = properties.get("callable");
if (p!=null) cacheCallable = p.getValueAsBoolean(cacheCallable);
p = properties.get("max");
if (p!=null) maxCacheSize = p.getValueAsInt(maxCacheSize);
if (cachePrepared && cacheCallable) {
this.types = ALL_TYPES;
} else if (cachePrepared) {
this.types = PREPARED_TYPE;
} else if (cacheCallable) {
this.types = CALLABLE_TYPE;
} else {
this.types = NO_TYPE;
}
}
/*end properties for the statement cache*/
/*begin the cache size*/
private static ConcurrentHashMap<ConnectionPool,AtomicInteger> cacheSizeMap =
new ConcurrentHashMap<>();
private AtomicInteger cacheSize;
@Override
public void poolStarted(ConnectionPool pool) {
cacheSizeMap.putIfAbsent(pool, new AtomicInteger(0));
super.poolStarted(pool);
}
@Override
public void poolClosed(ConnectionPool pool) {
cacheSizeMap.remove(pool);
super.poolClosed(pool);
}
/*end the cache size*/
/*begin the actual statement cache*/
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
super.reset(parent, con);
if (parent==null) {
cacheSize = null;
this.pcon = null;
if (oname != null) {
JmxUtil.unregisterJmx(oname);
oname = null;
}
} else {
cacheSize = cacheSizeMap.get(parent);
this.pcon = con;
if (!pcon.getAttributes().containsKey(STATEMENT_CACHE_ATTR)) {
ConcurrentHashMap<CacheKey,CachedStatement> cache =
new ConcurrentHashMap<>();
pcon.getAttributes().put(STATEMENT_CACHE_ATTR,cache);
}
if (oname == null) {
String keyprop = ",JdbcInterceptor=" + getClass().getSimpleName();
oname = JmxUtil.registerJmx(pcon.getObjectName(), keyprop, this);
}
}
}
@Override
public void disconnected(ConnectionPool parent, PooledConnection con, boolean finalizing) {
@SuppressWarnings("unchecked")
ConcurrentHashMap<CacheKey,CachedStatement> statements =
(ConcurrentHashMap<CacheKey,CachedStatement>)con.getAttributes().get(STATEMENT_CACHE_ATTR);
if (statements!=null) {
for (Map.Entry<CacheKey, CachedStatement> p : statements.entrySet()) {
closeStatement(p.getValue());
}
statements.clear();
}
super.disconnected(parent, con, finalizing);
}
public void closeStatement(CachedStatement st) {
if (st==null) return;
st.forceClose();
}
@Override
protected Object createDecorator(Object proxy, Method method, Object[] args,
Object statement, Constructor<?> constructor, String sql)
throws InstantiationException, IllegalAccessException, InvocationTargetException {
boolean process = process(this.types, method, false);
if (process) {
Object result = null;
CachedStatement statementProxy = new CachedStatement((PreparedStatement)statement,sql);
result = constructor.newInstance(new Object[] { statementProxy });
statementProxy.setActualProxy(result);
statementProxy.setConnection(proxy);
statementProxy.setConstructor(constructor);
statementProxy.setCacheKey(createCacheKey(method, args));
return result;
} else {
return super.createDecorator(proxy, method, args, statement, constructor, sql);
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
boolean process = process(this.types, method, false);
if (process && args.length>0 && args[0] instanceof String) {
CachedStatement statement = isCached(method, args);
if (statement!=null) {
//remove it from the cache since it is used
removeStatement(statement);
return statement.getActualProxy();
} else {
return super.invoke(proxy, method, args);
}
} else {
return super.invoke(proxy,method,args);
}
}
/**
* @param sql The SQL to attempt to match to entries in the statement cache
*
* @return The CachedStatement for the given SQL
*
* @deprecated Unused. Will be removed in Tomcat 9
*/
@Deprecated
public CachedStatement isCached(String sql) {
return null;
}
public CachedStatement isCached(Method method, Object[] args) {
ConcurrentHashMap<CacheKey,CachedStatement> cache = getCache();
if (cache == null) return null;
return cache.get(createCacheKey(method, args));
}
public boolean cacheStatement(CachedStatement proxy) {
ConcurrentHashMap<CacheKey,CachedStatement> cache = getCache();
if (cache == null) return false;
if (proxy.getCacheKey()==null) {
return false;
} else if (cache.containsKey(proxy.getCacheKey())) {
return false;
} else if (cacheSize.get()>=maxCacheSize) {
return false;
} else if (cacheSize.incrementAndGet()>maxCacheSize) {
cacheSize.decrementAndGet();
return false;
} else {
//cache the statement
cache.put(proxy.getCacheKey(), proxy);
return true;
}
}
public boolean removeStatement(CachedStatement proxy) {
ConcurrentHashMap<CacheKey,CachedStatement> cache = getCache();
if (cache == null) return false;
if (cache.remove(proxy.getCacheKey()) != null) {
cacheSize.decrementAndGet();
return true;
} else {
return false;
}
}
/*end the actual statement cache*/
protected ConcurrentHashMap<CacheKey,CachedStatement> getCache() {
PooledConnection pCon = this.pcon;
if (pCon == null) {
if (log.isWarnEnabled()) log.warn("Connection has already been closed or abandoned");
return null;
}
@SuppressWarnings("unchecked")
ConcurrentHashMap<CacheKey,CachedStatement> cache =
(ConcurrentHashMap<CacheKey,CachedStatement>)pCon.getAttributes().get(STATEMENT_CACHE_ATTR);
return cache;
}
@Override
public int getCacheSizePerConnection() {
ConcurrentHashMap<CacheKey,CachedStatement> cache = getCache();
if (cache == null) return 0;
return cache.size();
}
protected class CachedStatement extends StatementDecoratorInterceptor.StatementProxy<PreparedStatement> {
boolean cached = false;
CacheKey key;
public CachedStatement(PreparedStatement parent, String sql) {
super(parent, sql);
}
@Override
public void closeInvoked() {
//should we cache it
boolean shouldClose = true;
if (cacheSize.get() < maxCacheSize) {
//cache a proxy so that we don't reuse the facade
CachedStatement proxy = new CachedStatement(getDelegate(),getSql());
proxy.setCacheKey(getCacheKey());
try {
// clear Resultset
ResultSet result = getDelegate().getResultSet();
if (result != null && !result.isClosed()) {
result.close();
}
// clear parameter
getDelegate().clearParameters();
//create a new facade
Object actualProxy = getConstructor().newInstance(new Object[] { proxy });
proxy.setActualProxy(actualProxy);
proxy.setConnection(getConnection());
proxy.setConstructor(getConstructor());
if (cacheStatement(proxy)) {
proxy.cached = true;
shouldClose = false;
}
} catch (RuntimeException | ReflectiveOperationException | SQLException x) {
removeStatement(proxy);
}
}
if (shouldClose) {
super.closeInvoked();
}
closed = true;
delegate = null;
}
public void forceClose() {
removeStatement(this);
super.closeInvoked();
}
public CacheKey getCacheKey() {
return key;
}
public void setCacheKey(CacheKey cacheKey) {
key = cacheKey;
}
}
protected CacheKey createCacheKey(Method method, Object[] args) {
return createCacheKey(method.getName(), args);
}
protected CacheKey createCacheKey(String methodName, Object[] args) {
CacheKey key = null;
if (compare(PREPARE_STATEMENT, methodName)) {
key = new CacheKey(PREPARE_STATEMENT, args);
} else if (compare(PREPARE_CALL, methodName)) {
key = new CacheKey(PREPARE_CALL, args);
}
return key;
}
private static final class CacheKey {
private final String stmtType;
private final Object[] args;
private CacheKey(String type, Object[] methodArgs) {
stmtType = type;
args = methodArgs;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.deepHashCode(args);
result = prime * result
+ ((stmtType == null) ? 0 : stmtType.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CacheKey other = (CacheKey) obj;
if (!Arrays.deepEquals(args, other.args))
return false;
if (stmtType == null) {
if (other.stmtType != null)
return false;
} else if (!stmtType.equals(other.stmtType))
return false;
return true;
}
}
}

View File

@@ -0,0 +1,26 @@
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
import java.util.concurrent.atomic.AtomicInteger;
public interface StatementCacheMBean {
public boolean isCachePrepared();
public boolean isCacheCallable();
public int getMaxCacheSize();
public AtomicInteger getCacheSize();
public int getCacheSizePerConnection();
}

View File

@@ -0,0 +1,298 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* Implementation of <b>JdbcInterceptor</b> that proxies resultSets and statements.
* @author Guillermo Fernandes
*/
public class StatementDecoratorInterceptor extends AbstractCreateStatementInterceptor {
private static final Log logger = LogFactory.getLog(StatementDecoratorInterceptor.class);
protected static final String EXECUTE_QUERY = "executeQuery";
protected static final String GET_GENERATED_KEYS = "getGeneratedKeys";
protected static final String GET_RESULTSET = "getResultSet";
protected static final String[] RESULTSET_TYPES = {EXECUTE_QUERY, GET_GENERATED_KEYS, GET_RESULTSET};
/**
* the constructor to create the resultSet proxies
*/
protected static volatile Constructor<?> resultSetConstructor = null;
@Override
public void closeInvoked() {
// nothing to do
}
protected Constructor<?> getResultSetConstructor() throws NoSuchMethodException {
if (resultSetConstructor == null) {
Class<?> proxyClass = Proxy.getProxyClass(StatementDecoratorInterceptor.class.getClassLoader(),
new Class[] { ResultSet.class });
resultSetConstructor = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
}
return resultSetConstructor;
}
/**
* Creates a statement interceptor to monitor query response times
*/
@Override
public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
try {
String name = method.getName();
Constructor<?> constructor = null;
String sql = null;
if (compare(CREATE_STATEMENT, name)) {
// createStatement
constructor = getConstructor(CREATE_STATEMENT_IDX, Statement.class);
} else if (compare(PREPARE_STATEMENT, name)) {
// prepareStatement
constructor = getConstructor(PREPARE_STATEMENT_IDX, PreparedStatement.class);
sql = (String)args[0];
} else if (compare(PREPARE_CALL, name)) {
// prepareCall
constructor = getConstructor(PREPARE_CALL_IDX, CallableStatement.class);
sql = (String)args[0];
} else {
// do nothing, might be a future unsupported method
// so we better bail out and let the system continue
return statement;
}
return createDecorator(proxy, method, args, statement, constructor, sql);
} catch (Exception x) {
if (x instanceof InvocationTargetException) {
Throwable cause = x.getCause();
if (cause instanceof ThreadDeath) {
throw (ThreadDeath) cause;
}
if (cause instanceof VirtualMachineError) {
throw (VirtualMachineError) cause;
}
}
logger.warn("Unable to create statement proxy for slow query report.", x);
}
return statement;
}
/**
* Creates a proxy for a Statement.
*
* @param proxy The proxy object on which the method that triggered
* the creation of the statement was called.
* @param method The method that was called on the proxy
* @param args The arguments passed as part of the method call to
* the proxy
* @param statement The statement object that is to be proxied
* @param constructor The constructor for the desired proxy
* @param sql The sql of of the statement
*
* @return A new proxy for the Statement
* @throws InstantiationException Couldn't instantiate object
* @throws IllegalAccessException Inaccessible constructor
* @throws InvocationTargetException Exception thrown from constructor
*/
protected Object createDecorator(Object proxy, Method method, Object[] args,
Object statement, Constructor<?> constructor, String sql)
throws InstantiationException, IllegalAccessException, InvocationTargetException {
Object result = null;
StatementProxy<Statement> statementProxy =
new StatementProxy<>((Statement)statement,sql);
result = constructor.newInstance(new Object[] { statementProxy });
statementProxy.setActualProxy(result);
statementProxy.setConnection(proxy);
statementProxy.setConstructor(constructor);
return result;
}
protected boolean isExecuteQuery(String methodName) {
return EXECUTE_QUERY.equals(methodName);
}
protected boolean isExecuteQuery(Method method) {
return isExecuteQuery(method.getName());
}
protected boolean isResultSet(Method method, boolean process) {
return process(RESULTSET_TYPES, method, process);
}
/**
* Class to measure query execute time.
*/
protected class StatementProxy<T extends java.sql.Statement> implements InvocationHandler {
protected boolean closed = false;
protected T delegate;
private Object actualProxy;
private Object connection;
private String sql;
private Constructor<?> constructor;
public StatementProxy(T delegate, String sql) {
this.delegate = delegate;
this.sql = sql;
}
public T getDelegate() {
return this.delegate;
}
public String getSql() {
return sql;
}
public void setConnection(Object proxy) {
this.connection = proxy;
}
public Object getConnection() {
return this.connection;
}
public void setActualProxy(Object proxy){
this.actualProxy = proxy;
}
public Object getActualProxy() {
return this.actualProxy;
}
public Constructor<?> getConstructor() {
return constructor;
}
public void setConstructor(Constructor<?> constructor) {
this.constructor = constructor;
}
public void closeInvoked() {
if (getDelegate()!=null) {
try {
getDelegate().close();
}catch (SQLException ignore) {
}
}
closed = true;
delegate = null;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (compare(TOSTRING_VAL,method)) {
return toString();
}
// was close invoked?
boolean close = compare(CLOSE_VAL, method);
// allow close to be called multiple times
if (close && closed)
return null;
// are we calling isClosed?
if (compare(ISCLOSED_VAL, method))
return Boolean.valueOf(closed);
// if we are calling anything else, bail out
if (closed)
throw new SQLException("Statement closed.");
if (compare(GETCONNECTION_VAL,method)){
return connection;
}
boolean process = false;
process = isResultSet(method, process);
// check to see if we are about to execute a query
// if we are executing, get the current time
Object result = null;
try {
// perform close cleanup
if (close) {
closeInvoked();
} else {
// execute the query
result = method.invoke(delegate, args);
}
} catch (Throwable t) {
if (t instanceof InvocationTargetException
&& t.getCause() != null) {
throw t.getCause();
} else {
throw t;
}
}
if (process && result != null) {
Constructor<?> cons = getResultSetConstructor();
result = cons.newInstance(new Object[]{new ResultSetProxy(actualProxy, result)});
}
return result;
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer(StatementProxy.class.getName());
buf.append("[Proxy=");
buf.append(System.identityHashCode(this));
buf.append("; Sql=");
buf.append(getSql());
buf.append("; Delegate=");
buf.append(getDelegate());
buf.append("; Connection=");
buf.append(getConnection());
buf.append("]");
return buf.toString();
}
}
protected class ResultSetProxy implements InvocationHandler {
private Object st;
private Object delegate;
public ResultSetProxy(Object st, Object delegate) {
this.st = st;
this.delegate = delegate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("getStatement")) {
return this.st;
} else {
try {
return method.invoke(this.delegate, args);
} catch (Throwable t) {
if (t instanceof InvocationTargetException
&& t.getCause() != null) {
throw t.getCause();
} else {
throw t;
}
}
}
}
}
}

View File

@@ -0,0 +1,116 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.interceptor;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.PoolProperties;
import org.apache.tomcat.jdbc.pool.PooledConnection;
/**
* Keeps track of statements associated with a connection and invokes close upon {@link java.sql.Connection#close()}
* Useful for applications that dont close the associated statements after being done with a connection.
*
*/
public class StatementFinalizer extends AbstractCreateStatementInterceptor {
private static final Log log = LogFactory.getLog(StatementFinalizer.class);
protected List<StatementEntry> statements = new LinkedList<>();
private boolean logCreationStack = false;
@Override
public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
try {
if (statement instanceof Statement)
statements.add(new StatementEntry((Statement)statement));
}catch (ClassCastException x) {
//ignore this one
}
return statement;
}
@SuppressWarnings("null") // st is not null when used
@Override
public void closeInvoked() {
while (!statements.isEmpty()) {
StatementEntry ws = statements.remove(0);
Statement st = ws.getStatement();
boolean shallClose = false;
try {
shallClose = st!=null && (!st.isClosed());
if (shallClose) {
st.close();
}
} catch (Exception ignore) {
if (log.isDebugEnabled()) {
log.debug("Unable to closed statement upon connection close.",ignore);
}
} finally {
if (logCreationStack && shallClose) {
log.warn("Statement created, but was not closed at:", ws.getAllocationStack());
}
}
}
}
@Override
public void setProperties(Map<String, PoolProperties.InterceptorProperty> properties) {
super.setProperties(properties);
PoolProperties.InterceptorProperty logProperty = properties.get("trace");
if (null != logProperty) {
logCreationStack = logProperty.getValueAsBoolean(logCreationStack);
}
}
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
statements.clear();
super.reset(parent, con);
}
protected class StatementEntry {
private WeakReference<Statement> statement;
private Throwable allocationStack;
public StatementEntry(Statement statement) {
this.statement = new WeakReference<>(statement);
if (logCreationStack) {
this.allocationStack = new Throwable();
}
}
public Statement getStatement() {
return statement.get();
}
public Throwable getAllocationStack() {
return allocationStack;
}
}
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mbeans-descriptors>
<mbean description="Reports " domain="tomcat.jdbc" group="jdbc-pool" name="SlowQueryReportJmx"
type="org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx">
<attribute description="The name of the connection pool this Jmx bean is representing" name="poolName" type="java.lang.String" writeable="false"/>
<attribute description="List of all registered connections pools" name="poolNames" type="[java.lang.String;" writeable="false"/>
<attribute description="All the recorded query stats. " name="slowQueriesCD" type="[javax.management.openmbean.CompositeData;" writeable="false"/>
<operation description="Clears all the query stats" impact="ACTION" name="resetStats" returnType="void"/>
<notification description="Notification sent out by the slow query report when a query exceeds the threshold" name="slow-query">
<notification-type>Slow query</notification-type>
</notification>
<notification description="Notification sent out by the slow query report when a query fails execution" name="failed-query">
<notification-type>Failed query execution</notification-type>
</notification>
</mbean>
</mbeans-descriptors>

View File

@@ -0,0 +1,87 @@
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.jmx;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
public interface ConnectionPoolMBean extends PoolConfiguration {
//=================================================================
// POOL STATS
//=================================================================
public int getSize();
public int getIdle();
public int getActive();
public int getNumIdle();
public int getNumActive();
public int getWaitCount();
public long getBorrowedCount();
public long getReturnedCount();
public long getCreatedCount();
public long getReleasedCount();
public long getReconnectedCount();
public long getRemoveAbandonedCount();
public long getReleasedIdleCount();
//=================================================================
// POOL OPERATIONS
//=================================================================
public void checkIdle();
public void checkAbandoned();
public void testIdle();
/**
* Purges all connections in the pool.
* For connections currently in use, these connections will be
* purged when returned on the pool. This call also
* purges connections that are idle and in the pool
* To only purge used/active connections see {@link #purgeOnReturn()}
*/
public void purge();
/**
* Purges connections when they are returned from the pool.
* This call does not purge idle connections until they are used.
* To purge idle connections see {@link #purge()}
*/
public void purgeOnReturn();
/**
* reset the statistics of this pool.
*/
public void resetStats();
//=================================================================
// POOL NOTIFICATIONS
//=================================================================
}

View File

@@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.pool.jmx;
import java.lang.management.ManagementFactory;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
public class JmxUtil {
private static final Log log = LogFactory.getLog(JmxUtil.class);
public static ObjectName registerJmx(ObjectName base, String keyprop, Object obj) {
ObjectName oname = null;
try {
oname = getObjectName(base, keyprop);
if (oname != null) ManagementFactory.getPlatformMBeanServer().registerMBean(obj, oname);
} catch (Exception e) {
log.error("Jmx registration failed.",e);
}
return oname;
}
public static void unregisterJmx(ObjectName oname) {
if (oname ==null) return;
try {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(oname);
} catch (Exception e) {
log.error("Jmx unregistration failed.",e);
}
}
private static ObjectName getObjectName(ObjectName base, String keyprop)
throws MalformedObjectNameException {
if (base == null) return null;
StringBuilder OnameStr = new StringBuilder(base.toString());
if (keyprop != null) OnameStr.append(keyprop);
ObjectName oname = new ObjectName(OnameStr.toString());
return oname;
}
}

View File

@@ -0,0 +1,406 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mbeans-descriptors>
<mbean name="TomcatJDBCPool"
description="Provides per diagnostic metrics and notifications for JDBC operations"
domain="tomcat"
group="jdbc"
type="org.apache.tomcat.jdbc.pool.DataSource">
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="size"
description="The number of established connections in the pool, idle and in use"
type="java.lang.Integer"
writeable="false"/>
<attribute name="idle"
description="The number of established connections in the pool that are idle"
type="java.lang.Integer"
writeable="false"/>
<attribute name="numIdle"
description="Same as the idle attribute"
type="java.lang.Integer"
writeable="false"/>
<attribute name="active"
description="The number of established connections in the pool that are in use"
type="java.lang.Integer"
writeable="false"/>
<attribute name="numActive"
description="Same as the active attribute"
type="java.lang.Integer"
writeable="false"/>
<attribute name="poolSweeperEnabled"
description="Returns true if the pool has a background thread running"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="url"
description="The JDBC url for this connection pool"
type="java.lang.String"
writeable="false"/>
<attribute name="driverClassName"
description="The JDBC driver class for this connection pool"
type="java.lang.String"
writeable="false"/>
<attribute name="defaultAutoCommit"
description="The JDBC auto commit setting for new connections"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="defaultReadOnly"
description="The JDBC read only setting for new connections"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="defaultTransactionIsolation"
description="The JDBC transaction isolation setting for new connections"
type="java.lang.Integer"
writeable="false"/>
<attribute name="connectionProperties"
description="The connection properties that will be set for new connections. Format of the string will be [propertyName=property;]*"
type="java.lang.String"
writeable="false"/>
<attribute name="defaultCatalog"
description="The JDBC transaction isolation setting for new connections"
type="java.lang.String"
writeable="false"/>
<attribute name="initialSize"
description="The number of connections opened at pool startup"
type="java.lang.Integer"
writeable="false"/>
<attribute name="maxActive"
description="The maximum number of open connections"
type="java.lang.Integer"
writeable="false"/>
<attribute name="maxIdle"
description="The max number of idle connections"
type="java.lang.Integer"
writeable="false"/>
<attribute name="minIdle"
description="The minimum number of open connections"
type="java.lang.Integer"
writeable="false"/>
<attribute name="maxWait"
description="The time to wait in milliseconds before an SQLException is thrown when a connection is requested"
type="java.lang.Integer"
writeable="false"/>
<attribute name="validationQuery"
description="The query to run during validation"
type="java.lang.String"
writeable="false"/>
<attribute name="validationQueryTimeout"
description="The timeout in seconds before a connection validation queries fail"
type="java.lang.Integer"
writeable="false" />
<attribute name="testOnBorrow"
description="True if validation happens when a connection is requested"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="testOnReturn"
description="True if validation happens when a connection is returned"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="testWhileIdle"
description="True if validation happens when a connection is not in use (idle)"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="timeBetweenEvictionRunsMillis"
description="Sleep time for background thread in between pool checks"
type="java.lang.Integer"
writeable="false"/>
<attribute name="numTestsPerEvictionRun"
description="Not in use"
type="java.lang.Integer"
writeable="false"/>
<attribute name="minEvictableIdleTimeMillis"
description="Minimum amount of time a connection stays idle before it is evicted"
type="java.lang.Integer"
writeable="false"/>
<attribute name="accessToUnderlyingConnectionAllowed"
description="Returns true if one can retrieve the actual JDBC connection"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="removeAbandoned"
description="Returns true if connection in use can be timed out"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="removeAbandonedTimeout"
description="Timeout in seconds for connections in use"
type="java.lang.Integer"
writeable="false"/>
<attribute name="logAbandoned"
description="If true, stack trace will be recorded and printed out for timed out connection"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="loginTimeout"
description="Not in use"
type="java.lang.Integer"
writeable="false"/>
<attribute name="name"
description="The name of the connection pool, will be used in the ObjectName of the actual pool"
type="java.lang.String"
writeable="false"/>
<attribute name="password"
description="For security purposes,this doesn't return anything"
type="java.lang.String"
writeable="false"/>
<attribute name="username"
description="The username used to open connections"
type="java.lang.String"
writeable="false"/>
<attribute name="validationInterval"
description="If larger than zero than validation will only occur after the interval milliseconds has passed"
type="java.lang.Long"
writeable="false"/>
<attribute name="initSQL"
description="An SQL executed once per connection, when it is established"
type="java.lang.String"
writeable="false"/>
<attribute name="testOnConnect"
description="Validate connection after connection has been established"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="jdbcInterceptors"
description="The interceptors configured for this pool"
type="java.lang.String"
writeable="false"/>
<attribute name="jmxEnabled"
description="Register the pool with JMX or not"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="fairQueue"
description="a fair queue is being used by the connection pool"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="abandonWhenPercentageFull"
description="Connections that have been abandoned isn't closed unless connections in use are above this percentage"
type="java.lang.Integer"
writeable="false"/>
<attribute name="maxAge"
description="Time in milliseconds to keep this connection alive even when used"
type="java.lang.Long"
writeable="false"/>
<attribute name="useEquals"
description="Set to true if you wish the ProxyConnection class to use String.equals and set to false when you wish to use == when comparing method names"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="useLock"
description="If true, use a lock when operations are performed on the connection object"
type="java.lang.Boolean"
is="false"
writeable="false"/>
<attribute name="suspectTimeout"
description="Timeout in seconds for connection that suspected to have been abandoned"
type="java.lang.Integer"
writeable="false"/>
<attribute name="rollbackOnReturn"
description="If autoCommit==false then the pool can terminate the transaction by calling rollback on the connection as it is returned to the pool"
type="java.lang.Boolean"
is="false"
writeable="false"/>
<attribute name="commitOnReturn"
description="If autoCommit==false then the pool can complete the transaction by calling commit on the connection as it is returned to the pool"
type="java.lang.Boolean"
is="false"
writeable="false"/>
<attribute name="alternateUsernameAllowed"
description="If true, getConnection(username,password) is allowed"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="dataSource"
description="Data source that is injected into the pool"
type="javax.sql.DataSource"
writeable="false"/>
<attribute name="dataSourceJNDI"
description="The JNDI name for a data source to be looked up"
type="java.lang.String"
writeable="false"/>
<attribute name="useDisposableConnectionFacade"
description="If true, connection pool is configured to use a connection facade to prevent re-use of connection after close() has been invoked"
type="java.lang.Boolean"
is="false"
writeable="false"/>
<attribute name="logValidationErrors"
description="Log errors during the validation phase to the log file"
type="java.lang.Boolean"
is="false"
writeable="false"/>
<attribute name="validatorClassName"
description="The name of validator class which implements org.apache.tomcat.jdbc.pool.Validator interface"
type="java.lang.String"
writeable="false"/>
<attribute name="waitCount"
description="The number of threads waiting for a connection"
type="java.lang.Integer"
writeable="false"/>
<attribute name="propagateInterruptState"
description="If true, propagate the interrupt state for a thread that has been interrupted"
type="java.lang.Boolean"
is="false"
writeable="false"/>
<attribute name="ignoreExceptionOnPreLoad"
description="If true, ignore error of connection creation while initializing the pool"
type="java.lang.Boolean"
is="true"
writeable="false"/>
<attribute name="useStatementFacade"
description="If true, connection pool is configured to wrap statements."
type="java.lang.Boolean"
is="false"
writeable="false"/>
<attribute name="borrowedCount"
description="The total number of connections borrowed from this pool"
type="java.lang.Long"
writeable="false"/>
<attribute name="createdCount"
description="The total number of connections created by this pool"
type="java.lang.Long"
writeable="false"/>
<attribute name="returnedCount"
description="The total number of connections returned to this pool"
type="java.lang.Long"
writeable="false"/>
<attribute name="releasedCount"
description="The total number of connections released from this pool"
type="java.lang.Long"
writeable="false"/>
<attribute name="reconnectedCount"
description="The total number of connections reconnected by this pool."
type="java.lang.Long"
writeable="false"/>
<attribute name="removeAbandonedCount"
description="The total number of connections released by remove abandoned."
type="java.lang.Long"
writeable="false"/>
<attribute name="releasedIdleCount"
description="The total number of connections released by eviction."
type="java.lang.Long"
writeable="false"/>
<operation name="checkIdle"
description="forces a check of idle connections"
impact="ACTION"
returnType="void" />
<operation name="checkAbandoned"
description="forces a check of abandoned connections"
impact="ACTION"
returnType="void" />
<operation name="testIdle"
description="forces a validation of abandoned connections"
impact="ACTION"
returnType="void" />
<operation name="purge"
description="Purges all connections in the pool"
impact="ACTION"
returnType="void" />
<operation name="purgeOnReturn"
description="Purges connections when they are returned from the pool"
impact="ACTION"
returnType="void" />
<operation name="resetStats"
description="reset the statistics of this pool."
impact="ACTION"
returnType="void" />
</mbean>
</mbeans-descriptors>

View File

@@ -0,0 +1,105 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.jdbc.bugs;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.apache.tomcat.jdbc.test.DefaultProperties;
public class Bug51582 {
public static void main(String[] args) throws SQLException {
org.apache.tomcat.jdbc.pool.DataSource datasource = null;
PoolConfiguration p = new DefaultProperties();
p.setJmxEnabled(true);
p.setTestOnBorrow(false);
p.setTestOnReturn(false);
p.setValidationInterval(1000);
p.setTimeBetweenEvictionRunsMillis(2000);
p.setMaxWait(2000);
p.setMinEvictableIdleTimeMillis(1000);
datasource = new org.apache.tomcat.jdbc.pool.DataSource();
datasource.setPoolProperties(p);
datasource.setJdbcInterceptors("org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx(threshold=200)");
ConnectionPool pool = datasource.createPool();
Connection con = pool.getConnection();
Statement st = con.createStatement();
try {
st.execute("DROP ALIAS SLEEP");
} catch (Exception ignore) {
// Ignore
}
st.execute("CREATE ALIAS SLEEP AS $$\nboolean sleep() {\n try {\n Thread.sleep(10000);\n return true; } catch (Exception x) {\n return false;\n }\n}\n$$;");
st.close();
con.close();
int iter = 0;
while ((iter++) < 10) {
final Connection connection = pool.getConnection();
final CallableStatement s = connection.prepareCall("{CALL SLEEP()}");
List<Thread> threadList = new ArrayList<>();
for (int l = 0; l < 3; l++) {
final int i = l;
Thread thread = new Thread() {
@Override
public void run() {
try {
if (i == 0) {
Thread.sleep(1000);
s.cancel();
} else if (i == 1) {
// or use some other statement which will block
// for a longer time
long start = System.currentTimeMillis();
System.out.println("[" + getName() +
"] Calling SP SLEEP");
s.execute();
System.out.println("[" + getName() +
"] Executed SP SLEEP [" +
(System.currentTimeMillis() - start) +
"]");
} else {
Thread.sleep(1000);
connection.close();
}
} catch (InterruptedException e) {
} catch (SQLException e) {
e.printStackTrace();
}
}
};
threadList.add(thread);
thread.start();
}
for (Thread t : threadList) {
try {
t.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More