initial commit

main
Jörg Prante 2 years ago
commit 21556e171e

13
.gitignore vendored

@ -0,0 +1,13 @@
/data
/work
/logs
/.idea
/target
.DS_Store
*.iml
/.settings
/.classpath
/.project
/.gradle
build
out

@ -0,0 +1,191 @@
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:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
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
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.

@ -0,0 +1,7 @@
This work is based upon
https://github.com/susom/database
as of 28 Dec 2021
License: Apache 2.0

@ -0,0 +1,32 @@
plugins {
id "de.marcphilipp.nexus-publish" version "0.4.0"
id "io.codearte.nexus-staging" version "0.21.1"
}
wrapper {
gradleVersion = "${project.property('gradle.wrapper.version')}"
distributionType = Wrapper.DistributionType.ALL
}
ext {
user = 'jprante'
name = 'database'
description = 'JDBC connection pool and utilities'
inceptionYear = '2018'
url = 'https://github.com/' + user + '/' + name
scmUrl = 'https://github.com/' + user + '/' + name
scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git'
scmDeveloperConnection = 'scm:git:ssh://git@github.com:' + user + '/' + name + '.git'
issueManagementSystem = 'Github'
issueManagementUrl = ext.scmUrl + '/issues'
licenseName = 'The Apache License, Version 2.0'
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
subprojects {
apply plugin: 'java-library'
apply from: rootProject.file('gradle/ide/idea.gradle')
apply from: rootProject.file('gradle/compile/java.gradle')
apply from: rootProject.file('gradle/test/junit5.gradle')
apply from: rootProject.file('gradle/publishing/publication.gradle')
}

@ -0,0 +1,11 @@
group = org.xbib
name = database
version = 0.0.1
org.gradle.warning.mode = ALL
gradle.wrapper.version = 7.3.2
h2.version = 1.4.200
mockito.version = 3.3.3
testcontainers.version = 1.16.2
derby.version = 10.15.2.0
oracle-client.version = 21.4.0.0

@ -0,0 +1,43 @@
apply plugin: 'java-library'
java {
modularity.inferModulePath.set(true)
}
compileJava {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
compileTestJava {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
jar {
manifest {
attributes('Implementation-Version': project.version)
}
}
task sourcesJar(type: Jar, dependsOn: classes) {
classifier 'sources'
from sourceSets.main.allSource
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier 'javadoc'
}
artifacts {
archives sourcesJar, javadocJar
}
tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:all,-fallthrough'
}
javadoc {
options.addStringOption('Xdoclint:none', '-quiet')
}

@ -0,0 +1,55 @@
apply plugin: 'org.xbib.gradle.plugin.asciidoctor'
configurations {
asciidoclet
}
dependencies {
asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}"
}
asciidoctor {
backends 'html5'
outputDir = file("${rootProject.projectDir}/docs")
separateOutputDirs = false
attributes 'source-highlighter': 'coderay',
idprefix: '',
idseparator: '-',
toc: 'left',
doctype: 'book',
icons: 'font',
encoding: 'utf-8',
sectlink: true,
sectanchors: true,
linkattrs: true,
imagesdir: 'img',
stylesheet: "${projectDir}/src/docs/asciidoc/css/foundation.css"
}
/*javadoc {
options.docletpath = configurations.asciidoclet.files.asType(List)
options.doclet = 'org.asciidoctor.Asciidoclet'
//options.overview = "src/docs/asciidoclet/overview.adoc"
options.addStringOption "-base-dir", "${projectDir}"
options.addStringOption "-attribute",
"name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}"
configure(options) {
noTimestamp = true
}
}*/
/*javadoc {
options.docletpath = configurations.asciidoclet.files.asType(List)
options.doclet = 'org.asciidoctor.Asciidoclet'
options.overview = "${rootProject.projectDir}/src/docs/asciidoclet/overview.adoc"
options.addStringOption "-base-dir", "${projectDir}"
options.addStringOption "-attribute",
"name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}"
options.destinationDirectory(file("${projectDir}/docs/javadoc"))
configure(options) {
noTimestamp = true
}
}*/

@ -0,0 +1,13 @@
apply plugin: 'idea'
idea {
module {
outputDir file('build/classes/java/main')
testOutputDir file('build/classes/java/test')
}
}
if (project.convention.findPlugin(JavaPluginConvention)) {
//sourceSets.main.output.classesDirs = file("build/classes/java/main")
//sourceSets.test.output.classesDirs = file("build/classes/java/test")
}

@ -0,0 +1,64 @@
apply plugin: "de.marcphilipp.nexus-publish"
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
artifact sourcesJar
artifact javadocJar
pom {
name = project.name
description = rootProject.ext.description
url = rootProject.ext.url
inceptionYear = rootProject.ext.inceptionYear
packaging = 'jar'
organization {
name = 'xbib'
url = 'https://xbib.org'
}
developers {
developer {
id = 'jprante'
name = 'Jörg Prante'
email = 'joergprante@gmail.com'
url = 'https://github.com/jprante'
}
}
scm {
url = rootProject.ext.scmUrl
connection = rootProject.ext.scmConnection
developerConnection = rootProject.ext.scmDeveloperConnection
}
issueManagement {
system = rootProject.ext.issueManagementSystem
url = rootProject.ext.issueManagementUrl
}
licenses {
license {
name = rootProject.ext.licenseName
url = rootProject.ext.licenseUrl
distribution = 'repo'
}
}
}
}
}
}
if (project.hasProperty("signing.keyId")) {
apply plugin: 'signing'
signing {
sign publishing.publications.mavenJava
}
}
nexusPublishing {
repositories {
sonatype {
username = project.property('ossrhUsername')
password = project.property('ossrhPassword')
packageGroup = "org.xbib"
}
}
}

@ -0,0 +1,11 @@
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
apply plugin: 'io.codearte.nexus-staging'
nexusStaging {
username = project.property('ossrhUsername')
password = project.property('ossrhPassword')
packageGroup = "org.xbib"
}
}

@ -0,0 +1,27 @@
def junitVersion = project.hasProperty('junit.version')?project.property('junit.version'):'5.6.2'
def hamcrestVersion = project.hasProperty('hamcrest.version')?project.property('hamcrest.version'):'2.2'
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}"
testImplementation "org.hamcrest:hamcrest-library:${hamcrestVersion}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
}
test {
useJUnitPlatform()
failFast = false
testLogging {
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'
}
afterSuite { desc, result ->
if (!desc.parent) {
println "\nTest result: ${result.resultType}"
println "Test summary: ${result.testCount} tests, " +
"${result.successfulTestCount} succeeded, " +
"${result.failedTestCount} failed, " +
"${result.skippedTestCount} skipped"
}
}
}

Binary file not shown.

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gradlew vendored

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# 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
#
# https://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.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
gradlew.bat vendored

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

@ -0,0 +1,26 @@
This connection pool implementation is a derived work from HikariCP Version 3.4.5 (May 2020)
https://github.com/brettwooldridge/HikariCP
published under Apache 2.0 License.
Motivations for the derived work:
- remove all dependencies
- remove everything with metrics
- remove everything with JMX
- remove everything with slf4j logging
- remove everything with OSGI, Hibernate, Spring, JNDI
- remove everything with javassist
- remove everything with suspend/resume
- fix module-info.java
- remove MacOS "milli second" clock
- clean up source code, packages, inheritances, inner classes refactoring, get rid of helper classes
- no system property dark magic
- no addDataSourceProperty magic, pass a Properties object always to PoolConfig which contains JDBC driver properties
- Java 11+
- JUnit 5+
- Gradle 6.4+
The result is an 88k jar.

@ -0,0 +1,4 @@
dependencies {
testImplementation "com.h2database:h2:${project.property('h2.version')}"
testImplementation "org.mockito:mockito-core:${project.property('mockito.version')}"
}

@ -0,0 +1,6 @@
module org.xbib.jdbc.connection.pool {
requires java.logging;
requires transitive java.sql;
exports org.xbib.jdbc.connection.pool;
exports org.xbib.jdbc.connection.pool.util;
}

@ -0,0 +1,20 @@
package org.xbib.jdbc.connection.pool;
public enum IsolationLevel {
TRANSACTION_NONE(0),
TRANSACTION_READ_UNCOMMITTED(1),
TRANSACTION_READ_COMMITTED(2),
TRANSACTION_REPEATABLE_READ(4),
TRANSACTION_SERIALIZABLE(8),
TRANSACTION_SQL_SERVER_SNAPSHOT_ISOLATION_LEVEL(4096);
private final int levelId;
IsolationLevel(int levelId) {
this.levelId = levelId;
}
public int getLevelId() {
return levelId;
}
}

@ -0,0 +1,666 @@
package org.xbib.jdbc.connection.pool;
import java.sql.Connection;
import java.util.Properties;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class PoolConfig {
private static final Logger logger = Logger.getLogger(PoolConfig.class.getName());
private static final AtomicLong POOL_COUNTER = new AtomicLong();
private static final long CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
private static final long VALIDATION_TIMEOUT = TimeUnit.SECONDS.toMillis(5);
private static final long IDLE_TIMEOUT = TimeUnit.MINUTES.toMillis(10);
private static final long MAX_LIFETIME = TimeUnit.MINUTES.toMillis(30);
private static final int DEFAULT_POOL_SIZE = 8;
private final Properties properties;
private volatile long connectionTimeout;
private volatile long validationTimeout;
private volatile long idleTimeout;
private volatile long leakDetectionThreshold;
private volatile long maxLifetime;
private volatile int maxPoolSize;
private volatile int minIdle;
private volatile String username;
private volatile String password;
private long initializationFailTimeout;
private String connectionInitSql;
private String connectionTestQuery;
private String dataSourceClassName;
private String driverClassName;
private String jdbcUrl;
private String poolName;
private String catalog;
private String schema;
private String transactionIsolationName;
private boolean isAutoCommit;
private boolean isReadOnly;
private boolean isIsolateInternalQueries;
private boolean isAllowPoolSuspension;
private long aliveBypassWindowMs;
private long housekeepingPeriodMs;
private DataSource dataSource;
private ThreadFactory threadFactory;
private ScheduledExecutorService scheduledExecutor;
/**
* Default constructor
*/
public PoolConfig() {
this(new Properties());
}
/**
* Construct a {@link PoolConfig} from the specified properties object.
*
* @param properties the name of the property file
*/
public PoolConfig(Properties properties) {
this.properties = properties;
this.minIdle = -1;
this.maxPoolSize = -1;
this.maxLifetime = MAX_LIFETIME;
this.connectionTimeout = CONNECTION_TIMEOUT;
this.validationTimeout = VALIDATION_TIMEOUT;
this.idleTimeout = IDLE_TIMEOUT;
this.initializationFailTimeout = -1;
this.isAutoCommit = true;
this.jdbcUrl = properties.getProperty("url");
this.aliveBypassWindowMs = TimeUnit.MILLISECONDS.toMillis(500);
this.housekeepingPeriodMs = TimeUnit.SECONDS.toMillis(30);
}
public String getCatalog() {
return catalog;
}
public void setCatalog(String catalog) {
this.catalog = catalog;
}
public long getConnectionTimeout() {
return connectionTimeout;
}
public void setConnectionTimeout(long connectionTimeoutMs) {
if (connectionTimeoutMs == 0) {
this.connectionTimeout = Integer.MAX_VALUE;
} else if (connectionTimeoutMs < 250) {
throw new IllegalArgumentException("connectionTimeout cannot be less than 250ms");
} else {
this.connectionTimeout = connectionTimeoutMs;
}
}
public long getIdleTimeout() {
return idleTimeout;
}
public void setIdleTimeout(long idleTimeoutMs) {
if (idleTimeoutMs < 0) {
throw new IllegalArgumentException("idleTimeout cannot be negative");
}
this.idleTimeout = idleTimeoutMs;
}
public long getLeakDetectionThreshold() {
return leakDetectionThreshold;
}
public void setLeakDetectionThreshold(long leakDetectionThresholdMs) {
this.leakDetectionThreshold = leakDetectionThresholdMs;
}
public long getMaxLifetime() {
return maxLifetime;
}
public void setMaxLifetime(long maxLifetimeMs) {
this.maxLifetime = maxLifetimeMs;
}
public int getMaximumPoolSize() {
return maxPoolSize;
}
public void setMaximumPoolSize(int maxPoolSize) {
if (maxPoolSize < 1) {
throw new IllegalArgumentException("maxPoolSize cannot be less than 1");
}
this.maxPoolSize = maxPoolSize;
}
public int getMinimumIdle() {
return minIdle;
}
public void setMinimumIdle(int minIdle) {
if (minIdle < 0) {
throw new IllegalArgumentException("minimumIdle cannot be negative");
}
this.minIdle = minIdle;
}
/**
* Get the default password to use for DataSource.getConnection(username, password) calls.
*
* @return the password
*/
public String getPassword() {
return password;
}
/**
* Set the default password to use for DataSource.getConnection(username, password) calls.
*
* @param password the password
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Get the default username used for DataSource.getConnection(username, password) calls.
*
* @return the username
*/
public String getUsername() {
return username;
}
/**
* Set the default username used for DataSource.getConnection(username, password) calls.
*
* @param username the username
*/
public void setUsername(String username) {
this.username = username;
}
public long getValidationTimeout() {
return validationTimeout;
}
public void setValidationTimeout(long validationTimeoutMs) {
if (validationTimeoutMs < 250) {
throw new IllegalArgumentException("validationTimeout cannot be less than 250ms");
}
this.validationTimeout = validationTimeoutMs;
}
/**
* Get the SQL query to be executed to test the validity of connections.
*
* @return the SQL query string, or null
*/
public String getConnectionTestQuery() {
return connectionTestQuery;
}
/**
* Set the SQL query to be executed to test the validity of connections. Using
* the JDBC4 <code>Connection.isValid()</code> method to test connection validity can
* be more efficient on some databases and is recommended.
*
* @param connectionTestQuery a SQL query string
*/
public void setConnectionTestQuery(String connectionTestQuery) {
this.connectionTestQuery = connectionTestQuery;
}
/**
* Get the SQL string that will be executed on all new connections when they are
* created, before they are added to the pool.
*
* @return the SQL to execute on new connections, or null
*/
public String getConnectionInitSql() {
return connectionInitSql;
}
/**
* Set the SQL string that will be executed on all new connections when they are
* created, before they are added to the pool. If this query fails, it will be
* treated as a failed connection attempt.
*
* @param connectionInitSql the SQL to execute on new connections
*/
public void setConnectionInitSql(String connectionInitSql) {
this.connectionInitSql = connectionInitSql;
}
/**
* Get the {@link DataSource} that has been explicitly specified to be wrapped by the
* pool.
*
* @return the {@link DataSource} instance, or null
*/
public DataSource getDataSource() {
return dataSource;
}
/**
* Set a {@link DataSource} for the pool to explicitly wrap. This setter is not
* available through property file based initialization.
*
* @param dataSource a specific {@link DataSource} to be wrapped by the pool
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* Get the name of the JDBC {@link DataSource} class used to create Connections.
*
* @return the fully qualified name of the JDBC {@link DataSource} class
*/
public String getDataSourceClassName() {
return dataSourceClassName;
}
/**
* Set the fully qualified class name of the JDBC {@link DataSource} that will be used create Connections.
*
* @param className the fully qualified name of the JDBC {@link DataSource} class
*/
public void setDataSourceClassName(String className) {
this.dataSourceClassName = className;
}
public Properties getProperties() {
return properties;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
Class<?> driverClass = attemptFromContextLoader(driverClassName);
try {
if (driverClass == null) {
driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
logger.log(Level.FINE, () -> "driver class found in the PoolConfig class classloader: " + driverClassName + " " + this.getClass().getClassLoader());
}
} catch (ClassNotFoundException e) {
logger.log(Level.SEVERE, "failed to load driver class from PoolConfig class classloader: " + driverClassName + " " + this.getClass().getClassLoader());
}
if (driverClass == null) {
throw new RuntimeException("failed to load driver class " + driverClassName + " in either of PoolConfig class loader or Thread context classloader");
}
try {
driverClass.getConstructor().newInstance();
this.driverClassName = driverClassName;
} catch (Exception e) {
throw new RuntimeException("Failed to instantiate class " + driverClassName, e);
}
}
/**
* Get the default auto-commit behavior of connections in the pool.
*
* @return the default auto-commit behavior of connections
*/
public boolean isAutoCommit() {
return isAutoCommit;
}
/**
* Set the default auto-commit behavior of connections in the pool.
*
* @param isAutoCommit the desired auto-commit default for connections
*/
public void setAutoCommit(boolean isAutoCommit) {
this.isAutoCommit = isAutoCommit;
}
/**
* Get the pool suspension behavior (allowed or disallowed).
*
* @return the pool suspension behavior
*/
public boolean isAllowPoolSuspension() {
return isAllowPoolSuspension;
}
/**
* Set whether or not pool suspension is allowed. There is a performance
* impact when pool suspension is enabled. Unless you need it (for a
* redundancy system for example) do not enable it.
*
* @param isAllowPoolSuspension the desired pool suspension allowance
*/
public void setAllowPoolSuspension(boolean isAllowPoolSuspension) {
this.isAllowPoolSuspension = isAllowPoolSuspension;
}
/**
* Get the pool initialization failure timeout. See {@code #setInitializationFailTimeout(long)}
* for details.
*
* @return the number of milliseconds before the pool initialization fails
* @see PoolConfig#setInitializationFailTimeout(long)
*/
public long getInitializationFailTimeout() {
return initializationFailTimeout;
}
/**
* Set the pool initialization failure timeout. This setting applies to pool
* initialization when {@link PoolDataSource} is constructed with a {@link PoolConfig},
* or when {@link PoolDataSource} is constructed using the no-arg constructor
* and {@link PoolDataSource#getConnection()} is called.
* <ul>
* <li>Any value greater than zero will be treated as a timeout for pool initialization.
* The calling thread will be blocked from continuing until a successful connection
* to the database, or until the timeout is reached. If the timeout is reached, then
* a {@code PoolInitializationException} will be thrown. </li>
* <li>A value of zero will <i>not</i> prevent the pool from starting in the
* case that a connection cannot be obtained. However, upon start the pool will
* attempt to obtain a connection and validate that the {@code connectionTestQuery}
* and {@code connectionInitSql} are valid. If those validations fail, an exception
* will be thrown. If a connection cannot be obtained, the validation is skipped
* and the the pool will start and continue to try to obtain connections in the
* background. This can mean that callers to {@code DataSource#getConnection()} may
* encounter exceptions. </li>
* <li>A value less than zero will bypass any connection attempt and validation during
* startup, and therefore the pool will start immediately. The pool will continue to
* try to obtain connections in the background. This can mean that callers to
* {@code DataSource#getConnection()} may encounter exceptions. </li>
* </ul>
* Note that if this timeout value is greater than or equal to zero (0), and therefore an
* initial connection validation is performed, this timeout does not override the
* {@code connectionTimeout} or {@code validationTimeout}; they will be honored before this
* timeout is applied. The default value is one millisecond.
*
* @param initializationFailTimeout the number of milliseconds before the
* pool initialization fails, or 0 to validate connection setup but continue with
* pool start, or less than zero to skip all initialization checks and start the
* pool without delay.
*/
public void setInitializationFailTimeout(long initializationFailTimeout) {
this.initializationFailTimeout = initializationFailTimeout;
}
/**
* Determine whether internal pool queries, principally aliveness checks, will be isolated in their own transaction
* via {@link Connection#rollback()}. Defaults to {@code false}.
*
* @return {@code true} if internal pool queries are isolated, {@code false} if not
*/
public boolean isIsolateInternalQueries() {
return isIsolateInternalQueries;
}
/**
* Configure whether internal pool queries, principally aliveness checks, will be isolated in their own transaction
* via {@link Connection#rollback()}. Defaults to {@code false}.
*
* @param isolate {@code true} if internal pool queries should be isolated, {@code false} if not
*/
public void setIsolateInternalQueries(boolean isolate) {
this.isIsolateInternalQueries = isolate;
}
/**
* Determine whether the Connections in the pool are in read-only mode.
*
* @return {@code true} if the Connections in the pool are read-only, {@code false} if not
*/
public boolean isReadOnly() {
return isReadOnly;
}
/**
* Configures the Connections to be added to the pool as read-only Connections.
*
* @param readOnly {@code true} if the Connections in the pool are read-only, {@code false} if not
*/
public void setReadOnly(boolean readOnly) {
this.isReadOnly = readOnly;
}
public String getPoolName() {
return poolName;
}
/**
* Set the name of the connection pool. This is primarily used for the MBean
* to uniquely identify the pool configuration.
*
* @param poolName the name of the connection pool to use
*/
public void setPoolName(String poolName) {
this.poolName = poolName;
}
/**
* Get the ScheduledExecutorService used for housekeeping.
*
* @return the executor
*/
public ScheduledExecutorService getScheduledExecutor() {
return scheduledExecutor;
}
/**
* Set the ScheduledExecutorService used for housekeeping.
*
* @param executor the ScheduledExecutorService
*/
public void setScheduledExecutor(ScheduledExecutorService executor) {
this.scheduledExecutor = executor;
}
public String getTransactionIsolation() {
return transactionIsolationName;
}
/**
* Get the default schema name to be set on connections.
*
* @return the default schema name
*/
public String getSchema() {
return schema;
}
/**
* Set the default schema name to be set on connections.
*
* @param schema the name of the default schema
*/
public void setSchema(String schema) {
this.schema = schema;
}
/**
* Set the default transaction isolation level. The specified value is the
* constant name from the <code>Connection</code> class, eg.
* <code>TRANSACTION_REPEATABLE_READ</code>.
*
* @param isolationLevel the name of the isolation level
*/
public void setTransactionIsolation(String isolationLevel) {
this.transactionIsolationName = isolationLevel;
}
public void setAliveBypassWindowMs(long aliveBypassWindowMs) {
this.aliveBypassWindowMs = aliveBypassWindowMs;
}
public long getAliveBypassWindowMs() {
return aliveBypassWindowMs;
}
public void setHousekeepingPeriodMs(long housekeepingPeriodMs) {
this.housekeepingPeriodMs = housekeepingPeriodMs;
}
public long getHousekeepingPeriodMs() {
return housekeepingPeriodMs;
}
/**
* Get the thread factory used to create threads.
*
* @return the thread factory (may be null, in which case the default thread factory is used)
*/
public ThreadFactory getThreadFactory() {
return threadFactory;
}
/**
* Set the thread factory to be used to create threads.
*
* @param threadFactory the thread factory (setting to null causes the default thread factory to be used)
*/
public void setThreadFactory(ThreadFactory threadFactory) {
this.threadFactory = threadFactory;
}
private Class<?> attemptFromContextLoader(final String driverClassName) {
final ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
if (threadContextClassLoader != null) {
try {
final Class<?> driverClass = threadContextClassLoader.loadClass(driverClassName);
logger.log(Level.FINE, "Driver class found in Thread context class loader:" +
driverClassName + " " + threadContextClassLoader);
return driverClass;
} catch (ClassNotFoundException e) {
logger.log(Level.FINE, "Driver class not found in Thread context class loader, trying classloader: " +
driverClassName + " " + threadContextClassLoader + " " + this.getClass().getClassLoader());
}
}
return null;
}
@SuppressWarnings("StatementWithEmptyBody")
public void validate() {
if (poolName == null) {
poolName = generatePoolName();
}
catalog = getNullIfEmpty(catalog);
connectionInitSql = getNullIfEmpty(connectionInitSql);
connectionTestQuery = getNullIfEmpty(connectionTestQuery);
transactionIsolationName = getNullIfEmpty(transactionIsolationName);
dataSourceClassName = getNullIfEmpty(dataSourceClassName);
driverClassName = getNullIfEmpty(driverClassName);
jdbcUrl = getNullIfEmpty(jdbcUrl);
if (dataSource != null) {
if (dataSourceClassName != null) {
logger.log(Level.WARNING, "using dataSource and ignoring dataSourceClassName: " + poolName);
}
} else if (dataSourceClassName != null) {
if (driverClassName != null) {
logger.log(Level.SEVERE, "cannot use driverClassName and dataSourceClassName together: " + poolName);
throw new IllegalStateException("cannot use driverClassName and dataSourceClassName together.");
} else if (jdbcUrl != null) {
logger.log(Level.WARNING, "using dataSourceClassName and ignoring jdbcUrl: " + poolName);
}
} else if (jdbcUrl != null) {
// ok
} else if (driverClassName != null) {
logger.log(Level.SEVERE, "jdbcUrl is required with driverClassName: " + poolName);
throw new IllegalArgumentException("jdbcUrl is required with driverClassName.");
} else {
logger.log(Level.SEVERE, "dataSource or dataSourceClassName or jdbcUrl is required: " + poolName);
throw new IllegalArgumentException("dataSource or dataSourceClassName or jdbcUrl is required.");
}
validateNumerics();
}
/**
* @return null if string is null or empty
*/
private static String getNullIfEmpty(final String text) {
return text == null ? null : text.trim().isEmpty() ? null : text.trim();
}
private void validateNumerics() {
if (maxLifetime != 0 && maxLifetime < TimeUnit.SECONDS.toMillis(30)) {
logger.log(Level.WARNING, "maxLifetime is less than 30000ms, setting to default ms: " +
poolName + " " + MAX_LIFETIME);
maxLifetime = MAX_LIFETIME;
}
if (leakDetectionThreshold > 0) {
if (leakDetectionThreshold < TimeUnit.SECONDS.toMillis(2) || (leakDetectionThreshold > maxLifetime && maxLifetime > 0)) {
logger.log(Level.WARNING, "leakDetectionThreshold is less than 2000ms or more than maxLifetime, disabling it: " +
poolName);
leakDetectionThreshold = 0;
}
}
if (connectionTimeout < 250) {
logger.log(Level.WARNING, "connectionTimeout is less than 250ms, setting to ms: " +
poolName + " " + CONNECTION_TIMEOUT);
connectionTimeout = CONNECTION_TIMEOUT;
}
if (validationTimeout < 250) {
logger.log(Level.WARNING, "validationTimeout is less than 250ms, setting to ms" +
poolName + " " + VALIDATION_TIMEOUT);
validationTimeout = VALIDATION_TIMEOUT;
}
if (maxPoolSize < 1) {
maxPoolSize = DEFAULT_POOL_SIZE;
}
if (minIdle < 0 || minIdle > maxPoolSize) {
minIdle = maxPoolSize;
}
if (idleTimeout + TimeUnit.SECONDS.toMillis(1) > maxLifetime && maxLifetime > 0 && minIdle < maxPoolSize) {
logger.log(Level.WARNING, "idleTimeout is close to or more than maxLifetime, disabling it:" + poolName);
idleTimeout = 0;
} else if (idleTimeout != 0 && idleTimeout < TimeUnit.SECONDS.toMillis(10) && minIdle < maxPoolSize) {
logger.log(Level.WARNING, "idleTimeout is less than 10000ms, setting to default ms: " +
poolName + " " + IDLE_TIMEOUT);
idleTimeout = IDLE_TIMEOUT;
} else if (idleTimeout != IDLE_TIMEOUT && idleTimeout != 0 && minIdle == maxPoolSize) {
logger.log(Level.WARNING, "idleTimeout has been set but has no effect because the pool is operating as a fixed size pool: " + poolName);
}
}
private static String generatePoolName() {
return "xbib-pool-jdbc-" + POOL_COUNTER.getAndIncrement();
}
}

@ -0,0 +1,224 @@
package org.xbib.jdbc.connection.pool;
import java.io.Closeable;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sql.DataSource;
/**
* The pooled DataSource.
*/
public class PoolDataSource implements DataSource, Closeable {
private static final Logger logger = Logger.getLogger(PoolDataSource.class.getName());
private final AtomicBoolean isShutdown = new AtomicBoolean();
private final PoolConfig configuration;
private Pool pool;
/**
* Construct a {@link PoolDataSource} with the specified configuration. The
* {@link PoolConfig} is copied and the pool is started by invoking this
* constructor.
* The {@link PoolConfig} can be modified without affecting the DataSource
* and used to initialize another DataSource instance.
*
* @param configuration a config instance
*/
public PoolDataSource(PoolConfig configuration)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
this.configuration = configuration;
pool = new Pool(configuration);
}
public Pool getPool() {
return pool;
}
/**
* {@inheritDoc}
*/
@Override
public Connection getConnection() throws SQLException {
if (isClosed()) {
throw new SQLException("PoolDataSource " + this + " has been closed");
}
Pool result = pool;
if (result == null) {
synchronized (this) {
result = pool;
if (result == null) {
configuration.validate();
logger.log(Level.INFO, "Starting: " + configuration.getPoolName());
try {
pool = result = new Pool(configuration);
} catch (Exception pie) {
throw new SQLException(pie);
}
logger.log(Level.INFO, "Start completed: " + configuration.getPoolName());
}
}
}
return result.getConnection();
}
/**
* {@inheritDoc}
*/
@Override
public Connection getConnection(String username, String password) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
/**
* {@inheritDoc}
*/
@Override
public PrintWriter getLogWriter() throws SQLException {
Pool p = pool;
return (p != null ? p.getUnwrappedDataSource().getLogWriter() : null);
}
/**
* {@inheritDoc}
*/
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
Pool p = pool;
if (p != null) {
p.getUnwrappedDataSource().setLogWriter(out);
}
}
/**
* {@inheritDoc}
*/
@Override
public void setLoginTimeout(int seconds) throws SQLException {
Pool p = pool;
if (p != null) {
p.getUnwrappedDataSource().setLoginTimeout(seconds);
}
}
/**
* {@inheritDoc}
*/
@Override
public int getLoginTimeout() throws SQLException {
Pool p = pool;
return (p != null ? p.getUnwrappedDataSource().getLoginTimeout() : 0);
}
/**
* {@inheritDoc}
*/
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException();
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return (T) this;
}
Pool p = pool;
if (p != null) {
final DataSource unwrappedDataSource = p.getUnwrappedDataSource();
if (iface.isInstance(unwrappedDataSource)) {
return (T) unwrappedDataSource;
}
if (unwrappedDataSource != null) {
return unwrappedDataSource.unwrap(iface);
}
}
throw new SQLException("wrapped DataSource is not an instance of " + iface);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
if (iface.isInstance(this)) {
return true;
}
Pool p = pool;
if (p != null) {
final DataSource unwrappedDataSource = p.getUnwrappedDataSource();
if (iface.isInstance(unwrappedDataSource)) {
return true;
}
if (unwrappedDataSource != null) {
return unwrappedDataSource.isWrapperFor(iface);
}
}
return false;
}
/**
* Evict a connection from the pool. If the connection has already been closed (returned to the pool)
* this may result in a "soft" eviction; the connection will be evicted sometime in the future if it is
* currently in use. If the connection has not been closed, the eviction is immediate.
*
* @param connection the connection to evict from the pool
*/
public void evictConnection(Connection connection) {
Pool p;
if (!isClosed() && (p = pool) != null) {
p.evictConnection(connection);
}
}
/**
* Shutdown the DataSource and its associated pool.
*/
@Override
public void close() {
if (isShutdown.getAndSet(true)) {
return;
}
Pool p = pool;
if (p != null) {
try {
logger.log(Level.INFO, () -> "shutdown initiated: " + configuration.getPoolName());
p.shutdown();
logger.log(Level.INFO, () -> "shutdown completed: " + configuration.getPoolName());
} catch (InterruptedException e) {
logger.log(Level.WARNING, "interrupted during closing: " + configuration.getPoolName() + " " + e.getMessage());
Thread.currentThread().interrupt();
}
}
}
/**
* Determine whether the DataSource has been closed.
*
* @return true if the DataSource has been closed, false otherwise
*/
public boolean isClosed() {
return isShutdown.get();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "PoolDataSource (" + pool + ")";
}
}

@ -0,0 +1,176 @@
package org.xbib.jdbc.connection.pool;
import org.xbib.jdbc.connection.pool.util.ClockSource;
import org.xbib.jdbc.connection.pool.util.FastList;
import org.xbib.jdbc.connection.pool.util.BagEntry;
import java.sql.Connection;
import java.sql.Statement;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Pool entry used in the Bag to track Connection instances.
*/
public class PoolEntry implements BagEntry {
private static final Logger logger = Logger.getLogger(PoolEntry.class.getName());
private static final AtomicIntegerFieldUpdater<PoolEntry> stateUpdater =
AtomicIntegerFieldUpdater.newUpdater(PoolEntry.class, "state");
private Connection connection;
private long lastAccessed;
private long lastBorrowed;
private volatile int state = 0;
private volatile boolean evict;
private volatile ScheduledFuture<?> endOfLife;
private final FastList<Statement> openStatements;
private final Pool pool;
private final boolean isReadOnly;
private final boolean isAutoCommit;
public PoolEntry(Connection connection, Pool pool, final boolean isReadOnly, final boolean isAutoCommit) {
this.connection = connection;
this.pool = pool;
this.isReadOnly = isReadOnly;
this.isAutoCommit = isAutoCommit;
this.lastAccessed = ClockSource.currentTime();
this.openStatements = new FastList<>(Statement.class, 16);
}
public Connection getConnection() {
return connection;
}
public Pool getPool() {
return pool;
}
public long getLastAccessed() {
return lastAccessed;
}
public long getLastBorrowed() {
return lastBorrowed;
}
/**
* Release this entry back to the pool.
*
* @param lastAccessed last access time-stamp
*/
public void recycle(final long lastAccessed) {
if (connection != null) {
this.lastAccessed = lastAccessed;
pool.recycle(this);
}
}
/**
* Set the end of life {@link ScheduledFuture}.
*
* @param endOfLife this PoolEntry/Connection's end of life {@link ScheduledFuture}
*/
public void setFutureEol(final ScheduledFuture<?> endOfLife) {
this.endOfLife = endOfLife;
}
public Connection createProxyConnection(final ProxyLeakTask leakTask, final long now) {
return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
}
public String getPoolName() {
return pool.toString();
}
public boolean isMarkedEvicted() {
return evict;
}
public void markEvicted() {
this.evict = true;
}
public void evict(final String closureReason) {
pool.closeConnection(this, closureReason);
}
/**
* Returns millis since lastBorrowed
*/
public long getMillisSinceBorrowed() {
return ClockSource.elapsedMillis(lastBorrowed);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
final long now = ClockSource.currentTime();
return connection
+ ", accessed " + ClockSource.elapsedDisplayString(lastAccessed, now) + " ago, "
+ stateToString();
}
/**
* {@inheritDoc}
*/
@Override
public int getState() {
return stateUpdater.get(this);
}
/**
* {@inheritDoc}
*/
@Override
public boolean compareAndSet(int expect, int update) {
return stateUpdater.compareAndSet(this, expect, update);
}
/**
* {@inheritDoc}
*/
@Override
public void setState(int update) {
stateUpdater.set(this, update);
}
public Connection close() {
ScheduledFuture<?> eol = endOfLife;
if (eol != null && !eol.isDone() && !eol.cancel(false)) {
logger.log(Level.WARNING, "maxLifeTime expiration task cancellation unexpectedly returned false for connection " + getPoolName() + " " + connection);
}
Connection con = connection;
connection = null;
endOfLife = null;
return con;
}
private String stateToString() {
switch (state) {
case STATE_IN_USE:
return "IN_USE";
case STATE_NOT_IN_USE:
return "NOT_IN_USE";
case STATE_REMOVED:
return "REMOVED";
case STATE_RESERVED:
return "RESERVED";
default:
return "Invalid";
}
}
}

@ -0,0 +1,9 @@
package org.xbib.jdbc.connection.pool;
@SuppressWarnings("serial")
public class PoolEntryException extends Exception {
public PoolEntryException(Throwable t) {
super(t);
}
}

@ -0,0 +1,14 @@
package org.xbib.jdbc.connection.pool;
@SuppressWarnings("serial")
public class PoolInitializationException extends RuntimeException {
/**
* Construct an exception, possibly wrapping the provided Throwable as the cause.
*
* @param t the Throwable to wrap
*/
public PoolInitializationException(Throwable t) {
super("Failed to initialize pool: " + t.getMessage(), t);
}
}

@ -0,0 +1,595 @@
package org.xbib.jdbc.connection.pool;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Date;
import java.sql.NClob;
import java.sql.Ref;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Map;
/**
* This is the proxy class for java.sql.CallableStatement.
*/
public class ProxyCallableStatement extends ProxyPreparedStatement implements CallableStatement {
public ProxyCallableStatement(ProxyConnection connection, CallableStatement statement) {
super(connection, statement);
}
@Override
public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException {
((CallableStatement) delegate).registerOutParameter(parameterIndex, sqlType);
}
@Override
public void registerOutParameter(int parameterIndex, int sqlType, int scale) throws SQLException {
((CallableStatement) delegate).registerOutParameter(parameterIndex, sqlType, scale);
}
@Override
public boolean wasNull() throws SQLException {
return ((CallableStatement) delegate).wasNull();
}
@Override
public String getString(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getString(parameterIndex);
}
@Override
public boolean getBoolean(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getBoolean(parameterIndex);
}
@Override
public byte getByte(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getByte(parameterIndex);
}
@Override
public short getShort(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getShort(parameterIndex);
}
@Override
public int getInt(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getInt(parameterIndex);
}
@Override
public long getLong(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getLong(parameterIndex);
}
@Override
public float getFloat(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getFloat(parameterIndex);
}
@Override
public double getDouble(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getDouble(parameterIndex);
}
@Override
public BigDecimal getBigDecimal(int parameterIndex, int scale) throws SQLException {
return ((CallableStatement) delegate).getBigDecimal(parameterIndex, scale);
}
@Override
public byte[] getBytes(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getBytes(parameterIndex);
}
@Override
public Date getDate(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getDate(parameterIndex);
}
@Override
public Time getTime(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getTime(parameterIndex);
}
@Override
public Timestamp getTimestamp(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getTimestamp(parameterIndex);
}
@Override
public Object getObject(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getObject(parameterIndex);
}
@Override
public BigDecimal getBigDecimal(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getBigDecimal(parameterIndex);
}
@Override
public Object getObject(int parameterIndex, Map<String, Class<?>> map) throws SQLException {
return ((CallableStatement) delegate).getObject(parameterIndex, map);
}
@Override
public Ref getRef(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getRef(parameterIndex);
}
@Override
public Blob getBlob(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getBlob(parameterIndex);
}
@Override
public Clob getClob(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getClob(parameterIndex);
}
@Override
public Array getArray(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getArray(parameterIndex);
}
@Override
public Date getDate(int parameterIndex, Calendar cal) throws SQLException {
return ((CallableStatement) delegate).getDate(parameterIndex, cal);
}
@Override
public Time getTime(int parameterIndex, Calendar cal) throws SQLException {
return ((CallableStatement) delegate).getTime(parameterIndex, cal);
}
@Override
public Timestamp getTimestamp(int parameterIndex, Calendar cal) throws SQLException {
return ((CallableStatement) delegate).getTimestamp(parameterIndex, cal);
}
@Override
public void registerOutParameter(int parameterIndex, int sqlType, String typeName) throws SQLException {
((CallableStatement) delegate).registerOutParameter(parameterIndex, sqlType, typeName);
}
@Override
public void registerOutParameter(String parameterName, int sqlType) throws SQLException {
((CallableStatement) delegate).registerOutParameter(parameterName, sqlType);
}
@Override
public void registerOutParameter(String parameterName, int sqlType, int scale) throws SQLException {
((CallableStatement) delegate).registerOutParameter(parameterName, sqlType, scale);
}
@Override
public void registerOutParameter(String parameterName, int sqlType, String typeName) throws SQLException {
((CallableStatement) delegate).registerOutParameter(parameterName, sqlType, typeName);
}
@Override
public URL getURL(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getURL(parameterIndex);
}
@Override
public void setURL(String parameterName, URL val) throws SQLException {
((CallableStatement) delegate).setURL(parameterName, val);
}
@Override
public void setNull(String parameterName, int sqlType) throws SQLException {
((CallableStatement) delegate).setNull(parameterName, sqlType);
}
@Override
public void setBoolean(String parameterName, boolean x) throws SQLException {
((CallableStatement) delegate).setBoolean(parameterName, x);
}
@Override
public void setByte(String parameterName, byte x) throws SQLException {
((CallableStatement) delegate).setByte(parameterName, x);
}
@Override
public void setShort(String parameterName, short x) throws SQLException {
((CallableStatement) delegate).setShort(parameterName, x);
}
@Override
public void setInt(String parameterName, int x) throws SQLException {
((CallableStatement) delegate).setInt(parameterName, x);
}
@Override
public void setLong(String parameterName, long x) throws SQLException {
((CallableStatement) delegate).setLong(parameterName, x);
}
@Override
public void setFloat(String parameterName, float x) throws SQLException {
((CallableStatement) delegate).setFloat(parameterName, x);
}
@Override
public void setDouble(String parameterName, double x) throws SQLException {
((CallableStatement) delegate).setDouble(parameterName, x);
}
@Override
public void setBigDecimal(String parameterName, BigDecimal x) throws SQLException {
((CallableStatement) delegate).setBigDecimal(parameterName, x);
}
@Override
public void setString(String parameterName, String x) throws SQLException {
((CallableStatement) delegate).setString(parameterName, x);
}
@Override
public void setBytes(String parameterName, byte[] x) throws SQLException {
((CallableStatement) delegate).setBytes(parameterName, x);
}
@Override
public void setDate(String parameterName, Date x) throws SQLException {
((CallableStatement) delegate).setDate(parameterName, x);
}
@Override
public void setTime(String parameterName, Time x) throws SQLException {
((CallableStatement) delegate).setTime(parameterName, x);
}
@Override
public void setTimestamp(String parameterName, Timestamp x) throws SQLException {
((CallableStatement) delegate).setTimestamp(parameterName, x);
}
@Override
public void setAsciiStream(String parameterName, InputStream x, int length) throws SQLException {
((CallableStatement) delegate).setAsciiStream(parameterName, x, length);
}
@Override
public void setBinaryStream(String parameterName, InputStream x, int length) throws SQLException {
((CallableStatement) delegate).setBinaryStream(parameterName, x, length);
}
@Override
public void setObject(String parameterName, Object x, int targetSqlType, int scale) throws SQLException {
((CallableStatement) delegate).setObject(parameterName, x, targetSqlType, scale);
}
@Override
public void setObject(String parameterName, Object x, int targetSqlType) throws SQLException {
((CallableStatement) delegate).setObject(parameterName, x, targetSqlType);
}
@Override
public void setObject(String parameterName, Object x) throws SQLException {
((CallableStatement) delegate).setObject(parameterName, x);
}
@Override
public void setCharacterStream(String parameterName, Reader reader, int length) throws SQLException {
((CallableStatement) delegate).setCharacterStream(parameterName, reader, length);
}
@Override
public void setDate(String parameterName, Date x, Calendar cal) throws SQLException {
((CallableStatement) delegate).setDate(parameterName, x, cal);
}
@Override
public void setTime(String parameterName, Time x, Calendar cal) throws SQLException {
((CallableStatement) delegate).setTime(parameterName, x, cal);
}
@Override
public void setTimestamp(String parameterName, Timestamp x, Calendar cal) throws SQLException {
((CallableStatement) delegate).setTimestamp(parameterName, x, cal);
}
@Override
public void setNull(String parameterName, int sqlType, String typeName) throws SQLException {
((CallableStatement) delegate).setNull(parameterName, sqlType, typeName);
}
@Override
public String getString(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getString(parameterName);
}
@Override
public boolean getBoolean(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getBoolean(parameterName);
}
@Override
public byte getByte(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getByte(parameterName);
}
@Override
public short getShort(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getShort(parameterName);
}
@Override
public int getInt(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getInt(parameterName);
}
@Override
public long getLong(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getLong(parameterName);
}
@Override
public float getFloat(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getFloat(parameterName);
}
@Override
public double getDouble(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getDouble(parameterName);
}
@Override
public byte[] getBytes(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getBytes(parameterName);
}
@Override
public Date getDate(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getDate(parameterName);
}
@Override
public Time getTime(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getTime(parameterName);
}
@Override
public Timestamp getTimestamp(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getTimestamp(parameterName);
}
@Override
public Object getObject(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getObject(parameterName);
}
@Override
public BigDecimal getBigDecimal(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getBigDecimal(parameterName);
}
@Override
public Object getObject(String parameterName, Map<String, Class<?>> map) throws SQLException {
return ((CallableStatement) delegate).getObject(parameterName, map);
}
@Override
public Ref getRef(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getRef(parameterName);
}
@Override
public Blob getBlob(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getBlob(parameterName);
}
@Override
public Clob getClob(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getClob(parameterName);
}
@Override
public Array getArray(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getArray(parameterName);
}
@Override
public Date getDate(String parameterName, Calendar cal) throws SQLException {
return ((CallableStatement) delegate).getDate(parameterName, cal);
}
@Override
public Time getTime(String parameterName, Calendar cal) throws SQLException {
return ((CallableStatement) delegate).getTime(parameterName, cal);
}
@Override
public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLException {
return ((CallableStatement) delegate).getTimestamp(parameterName, cal);
}
@Override
public URL getURL(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getURL(parameterName);
}
@Override
public RowId getRowId(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getRowId(parameterIndex);
}
@Override
public RowId getRowId(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getRowId(parameterName);
}
@Override
public void setRowId(String parameterName, RowId x) throws SQLException {
((CallableStatement) delegate).setRowId(parameterName, x);
}
@Override
public void setNString(String parameterName, String value) throws SQLException {
((CallableStatement) delegate).setNString(parameterName, value);
}
@Override
public void setNCharacterStream(String parameterName, Reader value, long length) throws SQLException {
((CallableStatement) delegate).setNCharacterStream(parameterName, value, length);
}
@Override
public void setNClob(String parameterName, NClob value) throws SQLException {
((CallableStatement) delegate).setNClob(parameterName, value);
}
@Override
public void setClob(String parameterName, Reader reader, long length) throws SQLException {
((CallableStatement) delegate).setClob(parameterName, reader, length);
}
@Override
public void setBlob(String parameterName, InputStream inputStream, long length) throws SQLException {
((CallableStatement) delegate).setBlob(parameterName, inputStream, length);
}
@Override
public void setNClob(String parameterName, Reader reader, long length) throws SQLException {
((CallableStatement) delegate).setNClob(parameterName, reader, length);
}
@Override
public NClob getNClob(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getNClob(parameterIndex);
}
@Override
public NClob getNClob(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getNClob(parameterName);
}
@Override
public void setSQLXML(String parameterName, SQLXML xmlObject) throws SQLException {
((CallableStatement) delegate).setSQLXML(parameterName, xmlObject);
}
@Override
public SQLXML getSQLXML(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getSQLXML(parameterIndex);
}
@Override
public SQLXML getSQLXML(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getSQLXML(parameterName);
}
@Override
public String getNString(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getNString(parameterIndex);
}
@Override
public String getNString(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getNString(parameterName);
}
@Override
public Reader getNCharacterStream(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getNCharacterStream(parameterIndex);
}
@Override
public Reader getNCharacterStream(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getNCharacterStream(parameterName);
}
@Override
public Reader getCharacterStream(int parameterIndex) throws SQLException {
return ((CallableStatement) delegate).getCharacterStream(parameterIndex);
}
@Override
public Reader getCharacterStream(String parameterName) throws SQLException {
return ((CallableStatement) delegate).getCharacterStream(parameterName);
}
@Override
public void setBlob(String parameterName, Blob x) throws SQLException {
((CallableStatement) delegate).setBlob(parameterName, x);
}
@Override
public void setClob(String parameterName, Clob x) throws SQLException {
((CallableStatement) delegate).setClob(parameterName, x);
}
@Override
public void setAsciiStream(String parameterName, InputStream x, long length) throws SQLException {
((CallableStatement) delegate).setAsciiStream(parameterName, x, length);
}
@Override
public void setBinaryStream(String parameterName, InputStream x, long length) throws SQLException {
((CallableStatement) delegate).setBinaryStream(parameterName, x, length);
}
@Override
public void setCharacterStream(String parameterName, Reader reader, long length) throws SQLException {
((CallableStatement) delegate).setCharacterStream(parameterName, reader, length);
}
@Override
public void setAsciiStream(String parameterName, InputStream x) throws SQLException {
((CallableStatement) delegate).setAsciiStream(parameterName, x);
}
@Override
public void setBinaryStream(String parameterName, InputStream x) throws SQLException {
((CallableStatement) delegate).setBinaryStream(parameterName, x);
}
@Override
public void setCharacterStream(String parameterName, Reader reader) throws SQLException {
((CallableStatement) delegate).setCharacterStream(parameterName, reader);
}
@Override
public void setNCharacterStream(String parameterName, Reader value) throws SQLException {
((CallableStatement) delegate).setNCharacterStream(parameterName, value);
}
@Override
public void setClob(String parameterName, Reader reader) throws SQLException {
((CallableStatement) delegate).setClob(parameterName, reader);
}
@Override
public void setBlob(String parameterName, InputStream inputStream) throws SQLException {
((CallableStatement) delegate).setBlob(parameterName, inputStream);
}
@Override
public void setNClob(String parameterName, Reader reader) throws SQLException {
((CallableStatement) delegate).setNClob(parameterName, reader);
}
@Override
public <T> T getObject(int parameterIndex, Class<T> type) throws SQLException {
return ((CallableStatement) delegate).getObject(parameterIndex, type);
}
@Override
public <T> T getObject(String parameterName, Class<T> type) throws SQLException {
return ((CallableStatement) delegate).getObject(parameterName, type);
}
}

@ -0,0 +1,697 @@
package org.xbib.jdbc.connection.pool;
import org.xbib.jdbc.connection.pool.util.ClockSource;
import org.xbib.jdbc.connection.pool.util.FastList;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLTimeoutException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This is the proxy class for java.sql.Connection.
*/
public class ProxyConnection implements Connection {
private static final Logger logger = Logger.getLogger(ProxyConnection.class.getName());
private static final int DIRTY_BIT_READONLY = 0b000001;
private static final int DIRTY_BIT_AUTOCOMMIT = 0b000010;
private static final int DIRTY_BIT_ISOLATION = 0b000100;
private static final int DIRTY_BIT_CATALOG = 0b001000;
private static final int DIRTY_BIT_NETTIMEOUT = 0b010000;
private static final int DIRTY_BIT_SCHEMA = 0b100000;
private static final Set<String> ERROR_STATES;
private static final Set<Integer> ERROR_CODES;
private Connection delegate;
private final PoolEntry poolEntry;
private final ProxyLeakTask leakTask;
private final FastList<Statement> openStatements;
private int dirtyBits;
private long lastAccess;
private boolean isCommitStateDirty;
private boolean isReadOnly;
private boolean isAutoCommit;
private int networkTimeout;
private int transactionIsolation;
private String dbcatalog;
private String dbschema;
static {
ERROR_STATES = new HashSet<>();
ERROR_STATES.add("0A000"); // FEATURE UNSUPPORTED
ERROR_STATES.add("57P01"); // ADMIN SHUTDOWN
ERROR_STATES.add("57P02"); // CRASH SHUTDOWN
ERROR_STATES.add("57P03"); // CANNOT CONNECT NOW
ERROR_STATES.add("01002"); // SQL92 disconnect error
ERROR_STATES.add("JZ0C0"); // Sybase disconnect error
ERROR_STATES.add("JZ0C1"); // Sybase disconnect error
ERROR_CODES = new HashSet<>();
ERROR_CODES.add(500150);
ERROR_CODES.add(2399);
}
public ProxyConnection(PoolEntry poolEntry,
Connection connection,
FastList<Statement> openStatements,
ProxyLeakTask leakTask,
long now,
boolean isReadOnly,
boolean isAutoCommit) {
this.poolEntry = poolEntry;
this.delegate = connection;
this.openStatements = openStatements;
this.leakTask = leakTask;
this.lastAccess = now;
this.isReadOnly = isReadOnly;
this.isAutoCommit = isAutoCommit;
}
public boolean isCommitStateDirty() {
return isCommitStateDirty;
}
/**
*
* {@inheritDoc}
*/
@Override
public final String toString() {
return getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegate;
}
public boolean getAutoCommitState() {
return isAutoCommit;
}
public String getCatalogState() {
return dbcatalog;
}
public String getSchemaState() {
return dbschema;
}
public int getTransactionIsolationState() {
return transactionIsolation;
}
public boolean getReadOnlyState() {
return isReadOnly;
}
public int getNetworkTimeoutState() {
return networkTimeout;
}
public PoolEntry getPoolEntry() {
return poolEntry;
}
public SQLException checkException(SQLException sqle) {
boolean evict = false;
SQLException nse = sqle;
for (int depth = 0; delegate != CLOSED_CONNECTION && nse != null && depth < 10; depth++) {
final String sqlState = nse.getSQLState();
if (sqlState != null && sqlState.startsWith("08")
|| nse instanceof SQLTimeoutException
|| ERROR_STATES.contains(sqlState)
|| ERROR_CODES.contains(nse.getErrorCode())) {
// broken connection
evict = true;
break;
} else {
nse = nse.getNextException();
}
}
if (evict) {
logger.log(Level.WARNING, "Connection marked as broken because of SQLSTATE(), ErrorCode(): " +
poolEntry.getPoolName() + " " + delegate + " " + nse.getSQLState() + " " + nse.getErrorCode(), nse);
leakTask.cancel();
poolEntry.evict("(connection is broken)");
delegate = CLOSED_CONNECTION;
}
return sqle;
}
public synchronized void untrackStatement(final Statement statement) {
openStatements.remove(statement);
}
public void markCommitStateDirty() {
if (isAutoCommit) {
lastAccess = ClockSource.currentTime();
} else {
isCommitStateDirty = true;
}
}
public void cancelLeakTask() {
leakTask.cancel();
}
private synchronized <T extends Statement> T trackStatement(final T statement) {
openStatements.add(statement);
return statement;
}
@SuppressWarnings("try")
private synchronized void closeStatements() {
final int size = openStatements.size();
if (size > 0) {
for (int i = 0; i < size && delegate != CLOSED_CONNECTION; i++) {
try (Statement ignored = openStatements.get(i)) {
// automatic resource cleanup
} catch (SQLException e) {
logger.log(Level.WARNING, "Connection marked as broken because of an exception closing open statements during Connection.close(): " +
poolEntry.getPoolName() + " " + delegate);
leakTask.cancel();
poolEntry.evict("(exception closing Statements during Connection.close())");
delegate = CLOSED_CONNECTION;
}
}
openStatements.clear();
}
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws SQLException {
closeStatements();
if (delegate != CLOSED_CONNECTION) {
leakTask.cancel();
try {
if (isCommitStateDirty && !isAutoCommit) {
delegate.rollback();
lastAccess = ClockSource.currentTime();
logger.log(Level.FINE, "Executed rollback on connection due to dirty commit state on close(): " + poolEntry.getPoolName() + " " + delegate);
}
if (dirtyBits != 0) {
//poolEntry.resetConnectionState(this, dirtyBits);
resetConnectionState(poolEntry.getConnection(), dirtyBits,
poolEntry.getPool().getConfig().getCatalog() ,
poolEntry.getPool().getConfig().getSchema());
lastAccess = ClockSource.currentTime();
}
delegate.clearWarnings();
} catch (SQLException e) {
// when connections are aborted, exceptions are often thrown that should not reach the application
if (!poolEntry.isMarkedEvicted()) {
throw checkException(e);
}
} finally {
delegate = CLOSED_CONNECTION;
poolEntry.recycle(lastAccess);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isClosed() throws SQLException {
return delegate == CLOSED_CONNECTION;
}
/**
* {@inheritDoc}
*/
@Override
public Statement createStatement() throws SQLException {
return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement()));
}
/**
* {@inheritDoc}
*/
@Override
public Statement createStatement(int resultSetType, int concurrency) throws SQLException {
return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement(resultSetType, concurrency)));
}
/**
* {@inheritDoc}
*/
@Override
public Statement createStatement(int resultSetType, int concurrency, int holdability) throws SQLException {
return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement(resultSetType, concurrency, holdability)));
}
/**
* {@inheritDoc}
*/
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql)));
}
@Override
public String nativeSQL(String sql) throws SQLException {
return delegate.nativeSQL(sql);
}
/**
* {@inheritDoc}
*/
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int concurrency) throws SQLException {
return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency)));
}
@Override
public Map<String, Class<?>> getTypeMap() throws SQLException {
return delegate.getTypeMap();
}
@Override
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
delegate.setTypeMap(map);
}
@Override
public void setHoldability(int holdability) throws SQLException {
delegate.setHoldability(holdability);
}
@Override
public int getHoldability() throws SQLException {
return delegate.getHoldability();
}
@Override
public Savepoint setSavepoint() throws SQLException {
return delegate.setSavepoint();
}
@Override
public Savepoint setSavepoint(String name) throws SQLException {
return delegate.setSavepoint(name);
}
/**
* {@inheritDoc}
*/
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int concurrency, int holdability) throws SQLException {
return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency, holdability)));
}
/**
* {@inheritDoc}
*/
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql)));
}
/**
* {@inheritDoc}
*/
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, autoGeneratedKeys)));
}
/**
* {@inheritDoc}
*/
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int concurrency) throws SQLException {
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, resultSetType, concurrency)));
}
/**
* {@inheritDoc}
*/
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int concurrency, int holdability) throws SQLException {
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, resultSetType, concurrency, holdability)));
}
/**
* {@inheritDoc}
*/
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnIndexes)));
}
/**
* {@inheritDoc}
*/
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnNames)));
}
@Override
public Clob createClob() throws SQLException {
return delegate.createClob();
}
@Override
public Blob createBlob() throws SQLException {
return delegate.createBlob();
}
@Override
public NClob createNClob() throws SQLException {
return delegate.createNClob();
}
@Override
public SQLXML createSQLXML() throws SQLException {
return delegate.createSQLXML();
}
@Override
public boolean isValid(int timeout) throws SQLException {
return delegate.isValid(timeout);
}
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
delegate.setClientInfo(name, value);
}
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
delegate.setClientInfo(properties);
}
@Override
public String getClientInfo(String name) throws SQLException {
return delegate.getClientInfo(name);
}
@Override
public Properties getClientInfo() throws SQLException {
return delegate.getClientInfo();
}
@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
return delegate.createArrayOf(typeName, elements);
}
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
return delegate.createStruct(typeName, attributes);
}
/**
* {@inheritDoc}
*/
@Override
public DatabaseMetaData getMetaData() throws SQLException {
markCommitStateDirty();
return ProxyFactory.getProxyDatabaseMetaData(this, delegate.getMetaData());
}
/**
* {@inheritDoc}
*/
@Override
public void commit() throws SQLException {
delegate.commit();
isCommitStateDirty = false;
lastAccess = ClockSource.currentTime();
}
/**
* {@inheritDoc}
*/
@Override
public void rollback() throws SQLException {
delegate.rollback();
isCommitStateDirty = false;
lastAccess = ClockSource.currentTime();
}
/**
* {@inheritDoc}
*/
@Override
public void rollback(Savepoint savepoint) throws SQLException {
delegate.rollback(savepoint);
isCommitStateDirty = false;
lastAccess = ClockSource.currentTime();
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
delegate.releaseSavepoint(savepoint);
}
/**
* {@inheritDoc}
*/
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
delegate.setAutoCommit(autoCommit);
isAutoCommit = autoCommit;
dirtyBits |= DIRTY_BIT_AUTOCOMMIT;
}
@Override
public boolean getAutoCommit() throws SQLException {
return delegate.getAutoCommit();
}
/**
* {@inheritDoc}
*/
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
delegate.setReadOnly(readOnly);
isReadOnly = readOnly;
isCommitStateDirty = false;
dirtyBits |= DIRTY_BIT_READONLY;
}
@Override
public boolean isReadOnly() throws SQLException {
return delegate.isReadOnly();
}
/**
* {@inheritDoc}
*/
@Override
public void setTransactionIsolation(int level) throws SQLException {
delegate.setTransactionIsolation(level);
transactionIsolation = level;
dirtyBits |= DIRTY_BIT_ISOLATION;
}
@Override
public int getTransactionIsolation() throws SQLException {
return delegate.getTransactionIsolation();
}
@Override
public SQLWarning getWarnings() throws SQLException {
return delegate.getWarnings();
}
@Override
public void clearWarnings() throws SQLException {
delegate.clearWarnings();
}
/**
* {@inheritDoc}
*/
@Override
public void setCatalog(String catalog) throws SQLException {
delegate.setCatalog(catalog);
dbcatalog = catalog;
dirtyBits |= DIRTY_BIT_CATALOG;
}
@Override
public String getCatalog() throws SQLException {
return delegate.getCatalog();
}
/**
* {@inheritDoc}
*/
@Override
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
delegate.setNetworkTimeout(executor, milliseconds);
networkTimeout = milliseconds;
dirtyBits |= DIRTY_BIT_NETTIMEOUT;
}
@Override
public int getNetworkTimeout() throws SQLException {
return delegate.getNetworkTimeout();
}
/**
* {@inheritDoc}
*/
@Override
public void setSchema(String schema) throws SQLException {
delegate.setSchema(schema);
dbschema = schema;
dirtyBits |= DIRTY_BIT_SCHEMA;
}
@Override
public String getSchema() throws SQLException {
return delegate.getSchema();
}
@Override
public void abort(Executor executor) throws SQLException {
delegate.abort(executor);
}
/**
* {@inheritDoc}
*/
@Override
public final boolean isWrapperFor(Class<?> iface) throws SQLException {
return iface.isInstance(delegate) || (delegate != null && delegate.isWrapperFor(iface));
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(delegate)) {
return (T) delegate;
} else if (delegate != null) {
return delegate.unwrap(iface);
}
throw new SQLException("Wrapped connection is not an instance of " + iface);
}
static final Connection CLOSED_CONNECTION = getClosedConnection();
private static Connection getClosedConnection() {
InvocationHandler handler = (proxy, method, args) -> {
final String methodName = method.getName();
if ("isClosed".equals(methodName)) {
return Boolean.TRUE;
} else if ("isValid".equals(methodName)) {
return Boolean.FALSE;
}
if ("abort".equals(methodName)) {
return Void.TYPE;
}
if ("close".equals(methodName)) {
return Void.TYPE;
} else if ("toString".equals(methodName)) {
return ProxyConnection.class.getCanonicalName();
}
throw new SQLException("connection is closed");
};
return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(),
new Class<?>[] { Connection.class }, handler);
}
private void resetConnectionState(Connection connection, int dirtyBits, String catalog, String schema) throws SQLException {
int resetBits = 0;
if ((dirtyBits & DIRTY_BIT_READONLY) != 0 && getReadOnlyState() != isReadOnly) {
connection.setReadOnly(isReadOnly);
resetBits |= DIRTY_BIT_READONLY;
}
if ((dirtyBits & DIRTY_BIT_AUTOCOMMIT) != 0 && getAutoCommitState() != isAutoCommit) {
connection.setAutoCommit(isAutoCommit);
resetBits |= DIRTY_BIT_AUTOCOMMIT;
}
if ((dirtyBits & DIRTY_BIT_ISOLATION) != 0 && getTransactionIsolationState() != transactionIsolation) {
connection.setTransactionIsolation(transactionIsolation);
resetBits |= DIRTY_BIT_ISOLATION;
}
if ((dirtyBits & DIRTY_BIT_CATALOG) != 0 && catalog != null && !catalog.equals(getCatalogState())) {
connection.setCatalog(catalog);
resetBits |= DIRTY_BIT_CATALOG;
}
if ((dirtyBits & DIRTY_BIT_NETTIMEOUT) != 0 && getNetworkTimeoutState() != networkTimeout) {
connection.setNetworkTimeout(Runnable::run, networkTimeout);
resetBits |= DIRTY_BIT_NETTIMEOUT;
}
if ((dirtyBits & DIRTY_BIT_SCHEMA) != 0 && schema != null && !schema.equals(getSchemaState())) {
connection.setSchema(schema);
resetBits |= DIRTY_BIT_SCHEMA;
}
if (resetBits != 0 && logger.isLoggable(Level.FINE)) {
final String string = stringFromResetBits(resetBits);
logger.log(Level.FINE, () -> "reset on connection: " + string + " " + connection);
}
}
/**
* This will create a string for debug logging. Given a set of "reset bits", this
* method will return a concatenated string, for example
* Input : 0b00110
* Output: "autoCommit, isolation"
*
* @param bits a set of "reset bits"
* @return a string of which states were reset
*/
private String stringFromResetBits(final int bits) {
final StringBuilder sb = new StringBuilder();
for (int ndx = 0; ndx < RESET_STATES.length; ndx++) {
if ((bits & (0b1 << ndx)) != 0) {
sb.append(RESET_STATES[ndx]).append(", ");
}
}
sb.setLength(sb.length() - 2); // trim trailing comma
return sb.toString();
}
private static final String[] RESET_STATES = {"readOnly", "autoCommit", "isolation", "catalog", "netTimeout", "schema"};
}

@ -0,0 +1,68 @@
package org.xbib.jdbc.connection.pool;
import org.xbib.jdbc.connection.pool.util.FastList;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* A factory class that produces proxies around instances of the standard JDBC interfaces.
*/
@SuppressWarnings("unused")
public class ProxyFactory {
private ProxyFactory() {
// unconstructable
}
/**
* Create a proxy for the specified {@link Connection} instance.
*
* @param poolEntry the PoolEntry holding pool state
* @param connection the raw database Connection
* @param openStatements a reusable list to track open Statement instances
* @param leakTask the ProxyLeakTask for this connection
* @param now the current timestamp
* @param isReadOnly the default readOnly state of the connection
* @param isAutoCommit the default autoCommit state of the connection
* @return a proxy that wraps the specified {@link Connection}
*/
public static Connection getProxyConnection(PoolEntry poolEntry,
Connection connection,
FastList<Statement> openStatements,
ProxyLeakTask leakTask,
long now,
boolean isReadOnly,
boolean isAutoCommit) {
return new ProxyConnection(poolEntry, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
}
public static Statement getProxyStatement(ProxyConnection connection,
Statement statement) {
return new ProxyStatement(connection, statement);
}
public static CallableStatement getProxyCallableStatement(ProxyConnection connection,
CallableStatement statement) {
return new ProxyCallableStatement(connection, statement);
}
public static PreparedStatement getProxyPreparedStatement(ProxyConnection connection,
PreparedStatement statement) {
return new ProxyPreparedStatement(connection, statement);
}
public static ResultSet getProxyResultSet(ProxyConnection connection,
ProxyStatement statement,
ResultSet resultSet) {
return new ProxyResultSet(connection, statement, resultSet);
}
public static DatabaseMetaData getProxyDatabaseMetaData(ProxyConnection connection,
DatabaseMetaData metaData) {
return new ProxyDatabaseMetaData(connection, metaData);
}
}

@ -0,0 +1,77 @@
package org.xbib.jdbc.connection.pool;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A Runnable that is scheduled in the future to report leaks.
* The ScheduledFuture is cancelled if the connection is closed before the leak time expires.
*/
public class ProxyLeakTask implements Runnable {
private static final Logger logger = Logger.getLogger(ProxyLeakTask.class.getName());
public static final ProxyLeakTask NO_LEAK;
private ScheduledFuture<?> scheduledFuture;
private String connectionName;
private Exception exception;
private String threadName;
private boolean isLeaked;
static {
NO_LEAK = new ProxyLeakTask() {
@Override
public void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) {
}
@Override
public void run() {
}
@Override
public void cancel() {
}
};
}
public ProxyLeakTask(final PoolEntry poolEntry) {
this.exception = new Exception("Apparent connection leak detected");
this.threadName = Thread.currentThread().getName();
this.connectionName = poolEntry.getConnection().toString();
}
private ProxyLeakTask() {
}
public void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) {
scheduledFuture = executorService.schedule(this, leakDetectionThreshold, TimeUnit.MILLISECONDS);
}
/**
* {@inheritDoc}
*/
@Override
public void run() {
isLeaked = true;
final StackTraceElement[] stackTrace = exception.getStackTrace();
final StackTraceElement[] trace = new StackTraceElement[stackTrace.length - 5];
System.arraycopy(stackTrace, 5, trace, 0, trace.length);
exception.setStackTrace(trace);
logger.log(Level.WARNING, "Connection leak detection triggered for on thread, stack trace follows: " + connectionName + " " + threadName, exception);
}
public void cancel() {
scheduledFuture.cancel(false);
if (isLeaked) {
logger.log(Level.INFO, "Previously reported leaked connection on thread was returned to the pool (unleaked: )" + connectionName + " " + threadName);
}
}
}

@ -0,0 +1,32 @@
package org.xbib.jdbc.connection.pool;
import java.util.concurrent.ScheduledExecutorService;
/**
* A factory for {@link ProxyLeakTask} Runnables that are scheduled in the future to report leaks.
*/
public class ProxyLeakTaskFactory {
private final ScheduledExecutorService executorService;
private long leakDetectionThreshold;
public ProxyLeakTaskFactory(final long leakDetectionThreshold, final ScheduledExecutorService executorService) {
this.executorService = executorService;
this.leakDetectionThreshold = leakDetectionThreshold;
}
public ProxyLeakTask schedule(final PoolEntry poolEntry) {
return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry);
}
public void updateLeakDetectionThreshold(final long leakDetectionThreshold) {
this.leakDetectionThreshold = leakDetectionThreshold;
}
private ProxyLeakTask scheduleNewTask(PoolEntry poolEntry) {
ProxyLeakTask task = new ProxyLeakTask(poolEntry);
task.schedule(executorService, leakDetectionThreshold);
return task;
}
}

@ -0,0 +1,329 @@
package org.xbib.jdbc.connection.pool;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.NClob;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
/**
* This is the proxy class for java.sql.PreparedStatement.
*/
public class ProxyPreparedStatement extends ProxyStatement implements PreparedStatement {
public ProxyPreparedStatement(ProxyConnection connection, PreparedStatement statement) {
super(connection, statement);
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute() throws SQLException {
connection.markCommitStateDirty();
return ((PreparedStatement) delegate).execute();
}
@Override
public void addBatch() throws SQLException {
((PreparedStatement) delegate).addBatch();
}
@Override
public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
((PreparedStatement) delegate).setCharacterStream(parameterIndex, reader, length);
}
@Override
public void setRef(int parameterIndex, Ref x) throws SQLException {
((PreparedStatement) delegate).setRef(parameterIndex, x);
}
@Override
public void setBlob(int parameterIndex, Blob x) throws SQLException {
((PreparedStatement) delegate).setBlob(parameterIndex, x);
}
@Override
public void setClob(int parameterIndex, Clob x) throws SQLException {
((PreparedStatement) delegate).setClob(parameterIndex, x);
}
@Override
public void setArray(int parameterIndex, Array x) throws SQLException {
((PreparedStatement) delegate).setArray(parameterIndex, x);
}
@Override
public ResultSetMetaData getMetaData() throws SQLException {
return ((PreparedStatement) delegate).getMetaData();
}
@Override
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
((PreparedStatement) delegate).setDate(parameterIndex, x);
}
@Override
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
((PreparedStatement) delegate).setTime(parameterIndex, x);
}
@Override
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
((PreparedStatement) delegate).setTimestamp(parameterIndex, x, cal);
}
@Override
public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
((PreparedStatement) delegate).setNull(parameterIndex, sqlType, typeName);
}
@Override
public void setURL(int parameterIndex, URL x) throws SQLException {
((PreparedStatement) delegate).setURL(parameterIndex, x);
}
@Override
public ParameterMetaData getParameterMetaData() throws SQLException {
return ((PreparedStatement) delegate).getParameterMetaData();
}
@Override
public void setRowId(int parameterIndex, RowId x) throws SQLException {
((PreparedStatement) delegate).setRowId(parameterIndex, x);
}
@Override
public void setNString(int parameterIndex, String value) throws SQLException {
((PreparedStatement) delegate).setNString(parameterIndex, value);
}
@Override
public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
((PreparedStatement) delegate).setNCharacterStream(parameterIndex, value, length);
}
@Override
public void setNClob(int parameterIndex, NClob value) throws SQLException {
((PreparedStatement) delegate).setNClob(parameterIndex, value);
}
@Override
public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
((PreparedStatement) delegate).setClob(parameterIndex, reader, length);
}
@Override
public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
((PreparedStatement) delegate).setBlob(parameterIndex, inputStream, length);
}
@Override
public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
((PreparedStatement) delegate).setNClob(parameterIndex, reader, length);
}
@Override
public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
((PreparedStatement) delegate).setSQLXML(parameterIndex, xmlObject);
}
@Override
public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
((PreparedStatement) delegate).setObject(parameterIndex, x, targetSqlType, scaleOrLength);
}
@Override
public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
((PreparedStatement) delegate).setAsciiStream(parameterIndex, x, length);
}
@Override
public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
((PreparedStatement) delegate).setBinaryStream(parameterIndex, x, length);
}
@Override
public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
((PreparedStatement) delegate).setCharacterStream(parameterIndex, reader, length);
}
@Override
public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
((PreparedStatement) delegate).setAsciiStream(parameterIndex, x);
}
@Override
public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
((PreparedStatement) delegate).setBinaryStream(parameterIndex, x);
}
@Override
public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
((PreparedStatement) delegate).setCharacterStream(parameterIndex, reader);
}
@Override
public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
((PreparedStatement) delegate).setNCharacterStream(parameterIndex, value);
}
@Override
public void setClob(int parameterIndex, Reader reader) throws SQLException {
((PreparedStatement) delegate).setClob(parameterIndex, reader);
}
@Override
public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
((PreparedStatement) delegate).setBlob(parameterIndex, inputStream);
}
@Override
public void setNClob(int parameterIndex, Reader reader) throws SQLException {
((PreparedStatement) delegate).setNClob(parameterIndex, reader);
}
/**
* {@inheritDoc}
*/
@Override
public ResultSet executeQuery() throws SQLException {
connection.markCommitStateDirty();
ResultSet resultSet = ((PreparedStatement) getDelegate()).executeQuery();
return ProxyFactory.getProxyResultSet(connection, this, resultSet);
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate() throws SQLException {
connection.markCommitStateDirty();
return ((PreparedStatement) getDelegate()).executeUpdate();
}
@Override
public void setNull(int parameterIndex, int sqlType) throws SQLException {
((PreparedStatement) delegate).setNull(parameterIndex, sqlType);
}
@Override
public void setBoolean(int parameterIndex, boolean x) throws SQLException {
((PreparedStatement) delegate).setBoolean(parameterIndex, x);
}
@Override
public void setByte(int parameterIndex, byte x) throws SQLException {
((PreparedStatement) delegate).setByte(parameterIndex, x);
}
@Override
public void setShort(int parameterIndex, short x) throws SQLException {
((PreparedStatement) delegate).setShort(parameterIndex, x);
}
@Override
public void setInt(int parameterIndex, int x) throws SQLException {
((PreparedStatement) delegate).setInt(parameterIndex, x);
}
@Override
public void setLong(int parameterIndex, long x) throws SQLException {
((PreparedStatement) delegate).setLong(parameterIndex, x);
}
@Override
public void setFloat(int parameterIndex, float x) throws SQLException {
((PreparedStatement) delegate).setFloat(parameterIndex, x);
}
@Override
public void setDouble(int parameterIndex, double x) throws SQLException {
((PreparedStatement) delegate).setDouble(parameterIndex, x);
}
@Override
public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
((PreparedStatement) delegate).setBigDecimal(parameterIndex, x);
}
@Override
public void setString(int parameterIndex, String x) throws SQLException {
((PreparedStatement) delegate).setString(parameterIndex, x);
}
@Override
public void setBytes(int parameterIndex, byte[] x) throws SQLException {
((PreparedStatement) delegate).setBytes(parameterIndex, x);
}
@Override
public void setDate(int parameterIndex, Date x) throws SQLException {
((PreparedStatement) delegate).setDate(parameterIndex, x);
}
@Override
public void setTime(int parameterIndex, Time x) throws SQLException {
((PreparedStatement) delegate).setTime(parameterIndex, x);
}
@Override
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
((PreparedStatement) delegate).setTimestamp(parameterIndex, x);
}
@Override
public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
((PreparedStatement) delegate).setAsciiStream(parameterIndex, x, length);
}
@Override
public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
((PreparedStatement) delegate).setUnicodeStream(parameterIndex, x, length);
}
@Override
public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
((PreparedStatement) delegate).setBinaryStream(parameterIndex, x, length);
}
@Override
public void clearParameters() throws SQLException {
((PreparedStatement) delegate).clearParameters();
}
@Override
public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
((PreparedStatement) delegate).setObject(parameterIndex, x, targetSqlType);
}
@Override
public void setObject(int parameterIndex, Object x) throws SQLException {
((PreparedStatement) delegate).setObject(parameterIndex, x);
}
/**
* {@inheritDoc}
*/
@Override
public long executeLargeUpdate() throws SQLException {
connection.markCommitStateDirty();
return ((PreparedStatement) getDelegate()).executeLargeUpdate();
}
}

@ -0,0 +1,394 @@
package org.xbib.jdbc.connection.pool;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
/**
* This is the proxy class for java.sql.Statement.
*/
public class ProxyStatement implements Statement {
protected final ProxyConnection connection;
protected final Statement delegate;
private boolean isClosed;
private ResultSet proxyResultSet;
public ProxyStatement(ProxyConnection connection, Statement statement) {
this.connection = connection;
this.delegate = statement;
}
public Statement getDelegate() {
return delegate;
}
@SuppressWarnings("unused")
public SQLException checkException(SQLException e) {
return connection.checkException(e);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
final String delegateToString = delegate.toString();
return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegateToString;
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws SQLException {
synchronized (this) {
if (isClosed) {
return;
}
isClosed = true;
}
connection.untrackStatement(delegate);
try {
delegate.close();
} catch (SQLException e) {
throw connection.checkException(e);
}
}
@Override
public int getMaxFieldSize() throws SQLException {
return delegate.getMaxFieldSize();
}
@Override
public void setMaxFieldSize(int max) throws SQLException {
delegate.setMaxFieldSize(max);
}
@Override
public int getMaxRows() throws SQLException {
return delegate.getMaxRows();
}
@Override
public void setMaxRows(int max) throws SQLException {
delegate.setMaxRows(max);
}
@Override
public void setEscapeProcessing(boolean enable) throws SQLException {
delegate.setEscapeProcessing(enable);
}
@Override
public int getQueryTimeout() throws SQLException {
return delegate.getQueryTimeout();
}
@Override
public void setQueryTimeout(int seconds) throws SQLException {
delegate.setQueryTimeout(seconds);
}
@Override
public void cancel() throws SQLException {
delegate.cancel();
}
@Override
public SQLWarning getWarnings() throws SQLException {
return delegate.getWarnings();
}
@Override
public void clearWarnings() throws SQLException {
delegate.clearWarnings();
}
@Override
public void setCursorName(String name) throws SQLException {
delegate.setCursorName(name);
}
/**
* {@inheritDoc}
*/
@Override
public Connection getConnection() throws SQLException {
return connection;
}
@Override
public boolean getMoreResults(int current) throws SQLException {
return delegate.getMoreResults(current);
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute(String sql) throws SQLException {
connection.markCommitStateDirty();
return delegate.execute(sql);
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
connection.markCommitStateDirty();
return delegate.execute(sql, autoGeneratedKeys);
}
/**
* {@inheritDoc}
*/
@Override
public ResultSet executeQuery(String sql) throws SQLException {
connection.markCommitStateDirty();
ResultSet resultSet = delegate.executeQuery(sql);
return ProxyFactory.getProxyResultSet(connection, this, resultSet);
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate(String sql) throws SQLException {
connection.markCommitStateDirty();
return delegate.executeUpdate(sql);
}
/**
* {@inheritDoc}
*/
@Override
public int[] executeBatch() throws SQLException {
connection.markCommitStateDirty();
return delegate.executeBatch();
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
connection.markCommitStateDirty();
return delegate.executeUpdate(sql, autoGeneratedKeys);
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
connection.markCommitStateDirty();
return delegate.executeUpdate(sql, columnIndexes);
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
connection.markCommitStateDirty();
return delegate.executeUpdate(sql, columnNames);
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
connection.markCommitStateDirty();
return delegate.execute(sql, columnIndexes);
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute(String sql, String[] columnNames) throws SQLException {
connection.markCommitStateDirty();
return delegate.execute(sql, columnNames);
}
@Override
public int getResultSetHoldability() throws SQLException {
return delegate.getResultSetHoldability();
}
@Override
public boolean isClosed() throws SQLException {
return delegate.isClosed();
}
@Override
public void setPoolable(boolean poolable) throws SQLException {
delegate.setPoolable(poolable);
}
@Override
public boolean isPoolable() throws SQLException {
return delegate.isPoolable();
}
@Override
public void closeOnCompletion() throws SQLException {
delegate.closeOnCompletion();
}
@Override
public boolean isCloseOnCompletion() throws SQLException {
return delegate.isCloseOnCompletion();
}
/**
* {@inheritDoc}
*/
@Override
public long[] executeLargeBatch() throws SQLException {
connection.markCommitStateDirty();
return delegate.executeLargeBatch();
}
/**
* {@inheritDoc}
*/
@Override
public long executeLargeUpdate(String sql) throws SQLException {
connection.markCommitStateDirty();
return delegate.executeLargeUpdate(sql);
}
/**
* {@inheritDoc}
*/
@Override
public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
connection.markCommitStateDirty();
return delegate.executeLargeUpdate(sql, autoGeneratedKeys);
}
/**
* {@inheritDoc}
*/
@Override
public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException {
connection.markCommitStateDirty();
return delegate.executeLargeUpdate(sql, columnIndexes);
}
/**
* {@inheritDoc}
*/
@Override
public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException {
connection.markCommitStateDirty();
return delegate.executeLargeUpdate(sql, columnNames);
}
/**
* {@inheritDoc}
*/
@Override
public ResultSet getResultSet() throws SQLException {
final ResultSet resultSet = delegate.getResultSet();
if (resultSet != null) {
if (proxyResultSet == null || ((ProxyResultSet) proxyResultSet).getDelegate() != resultSet) {
proxyResultSet = ProxyFactory.getProxyResultSet(connection, this, resultSet);
}
} else {
proxyResultSet = null;
}
return proxyResultSet;
}
@Override
public int getUpdateCount() throws SQLException {
return delegate.getUpdateCount();
}
@Override
public boolean getMoreResults() throws SQLException {
return delegate.getMoreResults();
}
@Override
public void setFetchDirection(int direction) throws SQLException {
delegate.setFetchDirection(direction);
}
@Override
public int getFetchDirection() throws SQLException {
return delegate.getFetchDirection();
}
@Override
public void setFetchSize(int rows) throws SQLException {
delegate.setFetchSize(rows);
}
@Override
public int getFetchSize() throws SQLException {
return delegate.getFetchSize();
}
@Override
public int getResultSetConcurrency() throws SQLException {
return delegate.getResultSetConcurrency();
}
@Override
public int getResultSetType() throws SQLException {
return delegate.getResultSetType();
}
@Override
public void addBatch(String sql) throws SQLException {
delegate.addBatch(sql);
}
@Override
public void clearBatch() throws SQLException {
delegate.clearBatch();
}
/**
* {@inheritDoc}
*/
@Override
public ResultSet getGeneratedKeys() throws SQLException {
ResultSet resultSet = delegate.getGeneratedKeys();
if (proxyResultSet == null || ((ProxyResultSet) proxyResultSet).getDelegate() != resultSet) {
proxyResultSet = ProxyFactory.getProxyResultSet(connection, this, resultSet);
}
return proxyResultSet;
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public final <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(delegate)) {
return (T) delegate;
} else if (delegate != null) {
return delegate.unwrap(iface);
}
throw new SQLException("Wrapped statement is not an instance of " + iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return delegate.isWrapperFor(iface);
}
}

@ -0,0 +1,333 @@
package org.xbib.jdbc.connection.pool.util;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.AbstractQueuedLongSynchronizer;
import java.util.concurrent.locks.LockSupport;
import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
* This is a specialized concurrent bag that achieves superior performance
* to {@link java.util.concurrent.LinkedBlockingQueue} and
* {@link java.util.concurrent.LinkedTransferQueue} for the purposes of a
* connection pool. It uses {@link ThreadLocal} storage when possible to avoid
* locks, but resorts to scanning a common collection if there are no
* available items in the {@link ThreadLocal} list. Not-in-use items in the
* {@link ThreadLocal} lists can be "stolen" when the borrowing thread has none
* of its own. It is a "lock-less" implementation using a specialized
* {@link AbstractQueuedLongSynchronizer} to manage cross-thread signaling.
* Note that items that are "borrowed" from the bag are not actually
* removed from any collection, so garbage collection will not occur
* even if the reference is abandoned. Thus care must be taken to
* {@link Bag#requite(T)} borrowed objects otherwise a memory leak will result.
* Only the {@link Bag#remove(T)} method can completely remove an object.
*
* @param <T> the templated type to store in the bag
*/
public class Bag<T extends BagEntry> implements AutoCloseable {
private static final Logger logger = Logger.getLogger(Bag.class.getName());
private final CopyOnWriteArrayList<T> sharedList;
private final boolean weakThreadLocals;
private final ThreadLocal<List<Object>> threadList;
private final BagStateListener listener;
private final AtomicInteger waiters;
private volatile boolean closed;
private final SynchronousQueue<T> handoffQueue;
private String lastMessage;
/**
* Construct a Bag with the specified listener.
*
* @param listener the IBagStateListener to attach to this bag
*/
public Bag(BagStateListener listener) {
this.listener = listener;
this.weakThreadLocals = useWeakThreadLocals();
this.handoffQueue = new SynchronousQueue<>(true);
this.waiters = new AtomicInteger();
this.sharedList = new CopyOnWriteArrayList<>();
if (weakThreadLocals) {
this.threadList = ThreadLocal.withInitial(() -> new ArrayList<>(16));
} else {
this.threadList = ThreadLocal.withInitial(() -> new FastList<>(BagEntry.class, 16));
}
}
public String getLastMessage() {
return lastMessage;
}
/**
* The method will borrow a BagEntry from the bag, blocking for the
* specified timeout if none are available.
*
* @param timeout how long to wait before giving up, in units of unit
* @param timeUnit a <code>TimeUnit</code> determining how to interpret the timeout parameter
* @return a borrowed instance from the bag or null if a timeout occurs
* @throws InterruptedException if interrupted while waiting
*/
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException {
// Try the thread-local list first
final List<Object> list = threadList.get();
for (int i = list.size() - 1; i >= 0; i--) {
final Object entry = list.remove(i);
@SuppressWarnings("unchecked")
final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;
if (bagEntry != null && bagEntry.compareAndSet(BagEntry.STATE_NOT_IN_USE, BagEntry.STATE_IN_USE)) {
return bagEntry;
}
}
final int waiting = waiters.incrementAndGet();
try {
for (T bagEntry : sharedList) {
if (bagEntry.compareAndSet(BagEntry.STATE_NOT_IN_USE, BagEntry.STATE_IN_USE)) {
if (waiting > 1) {
listener.addBagItem(waiting - 1);
}
return bagEntry;
}
}
listener.addBagItem(waiting);
timeout = timeUnit.toNanos(timeout);
do {
final long start = ClockSource.currentTime();
final T bagEntry = handoffQueue.poll(timeout, TimeUnit.NANOSECONDS);
if (bagEntry == null || bagEntry.compareAndSet(BagEntry.STATE_NOT_IN_USE, BagEntry.STATE_IN_USE)) {
return bagEntry;
}
timeout -= ClockSource.elapsedNanos(start);
} while (timeout > 10_000);
return null;
} finally {
waiters.decrementAndGet();
}
}
/**
* This method will return a borrowed object to the bag. Objects
* that are borrowed from the bag but never "requited" will result
* in a memory leak.
*
* @param bagEntry the value to return to the bag
* @throws NullPointerException if value is null
* @throws IllegalStateException if the bagEntry was not borrowed from the bag
*/
public void requite(final T bagEntry) {
bagEntry.setState(BagEntry.STATE_NOT_IN_USE);
for (int i = 0; waiters.get() > 0; i++) {
if (bagEntry.getState() != BagEntry.STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
return;
} else if ((i & 0xff) == 0xff) {
LockSupport.parkNanos(TimeUnit.MICROSECONDS.toNanos(10));
} else {
Thread.yield();
}
}
final List<Object> threadLocalList = threadList.get();
if (threadLocalList.size() < 50) {
threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
}
}
/**
* Add a new object to the bag for others to borrow.
*
* @param bagEntry an object to add to the bag
*/
public void add(final T bagEntry) {
if (closed) {
lastMessage = "Bag has been closed, ignoring add()";
logger.info(lastMessage);
throw new IllegalStateException("Bag has been closed, ignoring add()");
}
sharedList.add(bagEntry);
// spin until a thread takes it or none are waiting
while (waiters.get() > 0 && bagEntry.getState() == BagEntry.STATE_NOT_IN_USE && !handoffQueue.offer(bagEntry)) {
Thread.yield();
}
}
/**
* Remove a value from the bag. This method should only be called
* with objects obtained by {@link Bag#borrow(long, TimeUnit)} or
* {@link Bag#reserve(T)}.
*
* @param bagEntry the value to remove
* @return true if the entry was removed, false otherwise
* @throws IllegalStateException if an attempt is made to remove an object
* from the bag that was not borrowed or reserved first
*/
public boolean remove(final T bagEntry) {
if (!bagEntry.compareAndSet(BagEntry.STATE_IN_USE, BagEntry.STATE_REMOVED) &&
!bagEntry.compareAndSet(BagEntry.STATE_RESERVED, BagEntry.STATE_REMOVED) && !closed) {
lastMessage = "attempt to remove an object from the bag that was not borrowed or reserved: " + bagEntry;
logger.warning(lastMessage);
return false;
}
boolean removed = sharedList.remove(bagEntry);
if (!removed && !closed) {
lastMessage = "attempt to remove an object from the bag that does not exist: " + bagEntry;
logger.warning(lastMessage);
}
threadList.get().remove(bagEntry);
return removed;
}
/**
* Close the bag to further adds.
*/
@Override
public void close() {
closed = true;
}
/**
* This method provides a "snapshot" in time of the BagEntry
* items in the bag in the specified state. It does not "lock"
* or reserve items in any way. Call {@link Bag#reserve(T)}
* on items in list before performing any action on them.
*
* @param state one of the {@link BagEntry} states
* @return a possibly empty list of objects having the state specified
*/
public List<T> values(int state) {
final List<T> list = sharedList.stream()
.filter(e -> e.getState() == state)
.collect(Collectors.toList());
Collections.reverse(list);
return list;
}
/**
* This method provides a "snapshot" in time of the bag items. It
* does not "lock" or reserve items in any way. Call {@link Bag#reserve(T)}
* on items in the list, or understand the concurrency implications of
* modifying items, before performing any action on them.
*
* @return a possibly empty list of (all) bag items
*/
@SuppressWarnings("unchecked")
public List<T> values() {
return (List<T>) sharedList.clone();
}
/**
* The method is used to make an item in the bag "unavailable" for
* borrowing. It is primarily used when wanting to operate on items
* returned by the {@link Bag#values(int)} method. Items that are
* reserved can be removed from the bag via {@link Bag#remove(T)}
* without the need to unreserve them. Items that are not removed
* from the bag can be make available for borrowing again by calling
* the {@link Bag#unreserve(BagEntry)} method.
*
* @param bagEntry the item to reserve
* @return true if the item was able to be reserved, false otherwise
*/
public boolean reserve(T bagEntry) {
return bagEntry.compareAndSet(BagEntry.STATE_NOT_IN_USE, BagEntry.STATE_RESERVED);
}
/**
* This method is used to make an item reserved via {@link Bag#reserve(T)}
* available again for borrowing.
*
* @param bagEntry the item to unreserve
*/
@SuppressWarnings("SpellCheckingInspection")
public void unreserve(final T bagEntry) {
if (bagEntry.compareAndSet(BagEntry.STATE_RESERVED, BagEntry.STATE_NOT_IN_USE)) {
// spin until a thread takes it or none are waiting
while (waiters.get() > 0 && !handoffQueue.offer(bagEntry)) {
Thread.yield();
}
} else {
lastMessage = "attempt to relinquish an object to the bag that was not reserved: " + bagEntry;
logger.warning(lastMessage);
}
}
/**
* Get the number of threads pending (waiting) for an item from the
* bag to become available.
*
* @return the number of threads waiting for items from the bag
*/
public int getWaitingThreadCount() {
return waiters.get();
}
/**
* Get a count of the number of items in the specified state at the time of this call.
*
* @param state the state of the items to count
* @return a count of how many items in the bag are in the specified state
*/
public int getCount(final int state) {
int count = 0;
for (BagEntry e : sharedList) {
if (e.getState() == state) {
count++;
}
}
return count;
}
public int[] getStateCounts() {
final int[] states = new int[6];
for (BagEntry e : sharedList) {
++states[e.getState()];
}
states[4] = sharedList.size();
states[5] = waiters.get();
return states;
}
/**
* Get the total number of items in the bag.
*
* @return the number of items in the bag
*/
public int size() {
return sharedList.size();
}
public void dumpState() {
sharedList.forEach(entry -> logger.info(entry.toString()));
}
/**
* Determine whether to use WeakReferences based on whether there is a
* custom ClassLoader implementation sitting between this class and the
* System ClassLoader.
*
* @return true if we should use WeakReferences in our ThreadLocals, false otherwise
*/
private boolean useWeakThreadLocals() {
try {
if (System.getProperty("pool.jdbc.useWeakReferences") != null) {
return Boolean.getBoolean("pool.jdbc.useWeakReferences");
}
return getClass().getClassLoader() != ClassLoader.getSystemClassLoader();
} catch (SecurityException se) {
return true;
}
}
}

@ -0,0 +1,14 @@
package org.xbib.jdbc.connection.pool.util;
public interface BagEntry {
int STATE_NOT_IN_USE = 0;
int STATE_IN_USE = 1;
int STATE_REMOVED = -1;
int STATE_RESERVED = -2;
boolean compareAndSet(int expectState, int newState);
void setState(int newState);
int getState();
}

@ -0,0 +1,6 @@
package org.xbib.jdbc.connection.pool.util;
public interface BagStateListener {
void addBagItem(int waiting);
}

@ -0,0 +1,153 @@
package org.xbib.jdbc.connection.pool.util;
import java.util.concurrent.TimeUnit;
/**
* A resolution-independent provider of current time-stamps and elapsed time
* calculations.
*/
public interface ClockSource {
ClockSource CLOCK = ClockSourceFactory.create();
/**
* Get the current time-stamp (resolution is opaque).
*
* @return the current time-stamp
*/
static long currentTime() {
return CLOCK.currentTime0();
}
long currentTime0();
/**
* Convert an opaque time-stamp returned by currentTime() into
* milliseconds.
*
* @param time an opaque time-stamp returned by an instance of this class
* @return the time-stamp in milliseconds
*/
static long toMillis(long time) {
return CLOCK.toMillis0(time);
}
long toMillis0(long time);
/**
* Convert an opaque time-stamp returned by currentTime() into
* nanoseconds.
*
* @param time an opaque time-stamp returned by an instance of this class
* @return the time-stamp in nanoseconds
*/
static long toNanos(long time) {
return CLOCK.toNanos0(time);
}
long toNanos0(long time);
/**
* Convert an opaque time-stamp returned by currentTime() into an
* elapsed time in milliseconds, based on the current instant in time.
*
* @param startTime an opaque time-stamp returned by an instance of this class
* @return the elapsed time between startTime and now in milliseconds
*/
static long elapsedMillis(long startTime) {
return CLOCK.elapsedMillis0(startTime);
}
long elapsedMillis0(long startTime);
/**
* Get the difference in milliseconds between two opaque time-stamps returned
* by currentTime().
*
* @param startTime an opaque time-stamp returned by an instance of this class
* @param endTime an opaque time-stamp returned by an instance of this class
* @return the elapsed time between startTime and endTime in milliseconds
*/
static long elapsedMillis(long startTime, long endTime) {
return CLOCK.elapsedMillis0(startTime, endTime);
}
long elapsedMillis0(long startTime, long endTime);
/**
* Convert an opaque time-stamp returned by currentTime() into an
* elapsed time in milliseconds, based on the current instant in time.
*
* @param startTime an opaque time-stamp returned by an instance of this class
* @return the elapsed time between startTime and now in milliseconds
*/
static long elapsedNanos(long startTime) {
return CLOCK.elapsedNanos0(startTime);
}
long elapsedNanos0(long startTime);
/**
* Get the difference in nanoseconds between two opaque time-stamps returned
* by currentTime().
*
* @param startTime an opaque time-stamp returned by an instance of this class
* @param endTime an opaque time-stamp returned by an instance of this class
* @return the elapsed time between startTime and endTime in nanoseconds
*/
static long elapsedNanos(long startTime, long endTime) {
return CLOCK.elapsedNanos0(startTime, endTime);
}
long elapsedNanos0(long startTime, long endTime);
/**
* Return the specified opaque time-stamp plus the specified number of milliseconds.
*
* @param time an opaque time-stamp
* @param millis milliseconds to add
* @return a new opaque time-stamp
*/
static long plusMillis(long time, long millis) {
return CLOCK.plusMillis0(time, millis);
}
long plusMillis0(long time, long millis);
TimeUnit getSourceTimeUnit0();
/**
* Get a String representation of the elapsed time in appropriate magnitude terminology.
*
* @param startTime an opaque time-stamp
* @param endTime an opaque time-stamp
* @return a string representation of the elapsed time interval
*/
static String elapsedDisplayString(long startTime, long endTime) {
return CLOCK.elapsedDisplayString0(startTime, endTime);
}
default String elapsedDisplayString0(long startTime, long endTime) {
long elapsedNanos = elapsedNanos0(startTime, endTime);
StringBuilder sb = new StringBuilder(elapsedNanos < 0 ? "-" : "");
elapsedNanos = Math.abs(elapsedNanos);
for (TimeUnit unit : TIMEUNITS_DESCENDING) {
long converted = unit.convert(elapsedNanos, TimeUnit.NANOSECONDS);
if (converted > 0) {
sb.append(converted).append(TIMEUNIT_DISPLAY_VALUES[unit.ordinal()]);
elapsedNanos -= TimeUnit.NANOSECONDS.convert(converted, unit);
}
}
return sb.toString();
}
TimeUnit[] TIMEUNITS_DESCENDING = {TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MINUTES,
TimeUnit.SECONDS, TimeUnit.MILLISECONDS, TimeUnit.MICROSECONDS,
TimeUnit.NANOSECONDS};
String[] TIMEUNIT_DISPLAY_VALUES = {"ns", "µs", "ms", "s", "m", "h", "d"};
}

@ -0,0 +1,10 @@
package org.xbib.jdbc.connection.pool.util;
/**
* Factory class used to create a platform-specific ClockSource.
*/
public class ClockSourceFactory {
public static ClockSource create() {
return new NanosecondClockSource();
}
}

@ -0,0 +1,22 @@
package org.xbib.jdbc.connection.pool.util;
import java.util.concurrent.ThreadFactory;
public class DefaultThreadFactory implements ThreadFactory {
private final String threadName;
private final boolean daemon;
public DefaultThreadFactory(String threadName, boolean daemon) {
this.threadName = threadName;
this.daemon = daemon;
}
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, threadName);
thread.setDaemon(daemon);
return thread;
}
}

@ -0,0 +1,161 @@
package org.xbib.jdbc.connection.pool.util;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Enumeration;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class DriverDataSource implements DataSource {
private static final Logger logger = Logger.getLogger(DriverDataSource.class.getName());
private static final String PASSWORD = "password";
private static final String USER = "user";
private String jdbcUrl;
private final Properties driverProperties;
private Driver driver;
public DriverDataSource(String jdbcUrl, String driverClassName, Properties properties, String username, String password) {
this.jdbcUrl = jdbcUrl;
this.driverProperties = new Properties();
for (Entry<Object, Object> entry : properties.entrySet()) {
driverProperties.setProperty(entry.getKey().toString(), entry.getValue().toString());
}
if (username != null) {
setUser(username);
}
if (password != null) {
setPassword(password);
}
if (driverClassName != null) {
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver d = drivers.nextElement();
if (d.getClass().getName().equals(driverClassName)) {
driver = d;
break;
}
}
if (driver == null) {
logger.warning("Registered driver with driverClassName was not found, trying direct instantiation: " + driverClassName);
Class<?> driverClass = null;
ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
try {
if (threadContextClassLoader != null) {
try {
driverClass = threadContextClassLoader.loadClass(driverClassName);
logger.fine("Driver class found in Thread context class loader: " + driverClassName + " " + threadContextClassLoader);
} catch (ClassNotFoundException e) {
logger.fine("Driver class not found in Thread context class loader, trying classloader: " +
driverClassName + " " + threadContextClassLoader + " " + this.getClass().getClassLoader());
}
}
if (driverClass == null) {
driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
logger.fine("Driver class found in the PoolConfig class classloader:" + driverClassName + " " + this.getClass().getClassLoader());
}
} catch (ClassNotFoundException e) {
logger.fine("Failed to load driver class from PoolConfig class classloader: " + driverClassName + " " + this.getClass().getClassLoader());
}
if (driverClass != null) {
try {
driver = (Driver) driverClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
logger.log(Level.WARNING, "Failed to create instance of driver class, trying jdbcUrl resolution: " + driverClassName + " " + e.getMessage(), e);
}
}
}
}
final String sanitizedUrl = jdbcUrl.replaceAll("([?&;]password=)[^&#;]*(.*)", "$1<masked>$2");
try {
if (driver == null) {
driver = DriverManager.getDriver(jdbcUrl);
logger.fine("Loaded driver with class name for jdbcUrl: " + driver.getClass().getName() + " " + sanitizedUrl);
} else if (!driver.acceptsURL(jdbcUrl)) {
throw new RuntimeException("Driver " + driverClassName + " claims to not accept jdbcUrl, " + sanitizedUrl);
}
} catch (SQLException e) {
throw new RuntimeException("Failed to get driver instance for jdbcUrl=" + sanitizedUrl, e);
}
}
@Override
public Connection getConnection() throws SQLException {
return driver.connect(jdbcUrl, driverProperties);
}
public void setUrl(String url) {
this.jdbcUrl = url;
}
public void setUser(String user) {
driverProperties.put(USER, driverProperties.getProperty("user", user));
}
public void setPassword(String password) {
driverProperties.put(PASSWORD, driverProperties.getProperty("password", password));
}
@Override
public Connection getConnection(final String username, final String password) throws SQLException {
final Properties cloned = (Properties) driverProperties.clone();
if (username != null) {
cloned.put("user", username);
if (cloned.containsKey("username")) {
cloned.put("username", username);
}
}
if (password != null) {
cloned.put("password", password);
}
return driver.connect(jdbcUrl, cloned);
}
@Override
public PrintWriter getLogWriter() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setLogWriter(PrintWriter logWriter) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setLoginTimeout(int seconds) {
DriverManager.setLoginTimeout(seconds);
}
@Override
public int getLoginTimeout() {
return DriverManager.getLoginTimeout();
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return driver.getParentLogger();
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public boolean isWrapperFor(Class<?> iface) {
return false;
}
}

@ -0,0 +1,357 @@
package org.xbib.jdbc.connection.pool.util;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.RandomAccess;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
/**
* Fast list without range checking.
*/
public final class FastList<T> implements List<T>, RandomAccess {
private final Class<?> clazz;
private T[] elementData;
private int size;
/**
* Construct a FastList with a default size of 32.
*
* @param clazz the Class stored in the collection
*/
@SuppressWarnings("unchecked")
public FastList(Class<?> clazz) {
this.elementData = (T[]) Array.newInstance(clazz, 32);
this.clazz = clazz;
}
/**
* Construct a FastList with a specified size.
*
* @param clazz the Class stored in the collection
* @param capacity the initial size of the FastList
*/
@SuppressWarnings("unchecked")
public FastList(Class<?> clazz, int capacity) {
this.elementData = (T[]) Array.newInstance(clazz, capacity);
this.clazz = clazz;
}
/**
* Add an element to the tail of the FastList.
*
* @param element the element to add
*/
@Override
public boolean add(T element) {
if (size < elementData.length) {
elementData[size++] = element;
} else {
// overflow-conscious code
final int oldCapacity = elementData.length;
final int newCapacity = oldCapacity << 1;
@SuppressWarnings("unchecked")
final T[] newElementData = (T[]) Array.newInstance(clazz, newCapacity);
System.arraycopy(elementData, 0, newElementData, 0, oldCapacity);
newElementData[size++] = element;
elementData = newElementData;
}
return true;
}
/**
* Get the element at the specified index.
*
* @param index the index of the element to get
* @return the element, or ArrayIndexOutOfBounds is thrown if the index is invalid
*/
@Override
public T get(int index) {
return elementData[index];
}
/**
* Remove the last element from the list. No bound check is performed, so if this
* method is called on an empty list and ArrayIndexOutOfBounds exception will be
* thrown.
*
* @return the last element of the list
*/
public T removeLast() {
T element = elementData[--size];
elementData[size] = null;
return element;
}
/**
* This remove method is most efficient when the element being removed
* is the last element. Equality is identity based, not equals() based.
* Only the first matching element is removed.
*
* @param element the element to remove
*/
@Override
public boolean remove(Object element) {
for (int index = size - 1; index >= 0; index--) {
if (element == elementData[index]) {
final int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
elementData[--size] = null;
return true;
}
}
return false;
}
/**
* Clear the FastList.
*/
@Override
public void clear() {
for (int i = 0; i < size; i++) {
elementData[i] = null;
}
size = 0;
}
/**
* Get the current number of elements in the FastList.
*
* @return the number of current elements
*/
@Override
public int size() {
return size;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isEmpty() {
return size == 0;
}
/**
* {@inheritDoc}
*/
@Override
public T set(int index, T element) {
T old = elementData[index];
elementData[index] = element;
return old;
}
/**
* {@inheritDoc}
*/
@Override
public T remove(int index) {
if (size == 0) {
return null;
}
final T old = elementData[index];
final int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
elementData[--size] = null;
return old;
}
/**
* {@inheritDoc}
*/
@Override
public boolean contains(Object o) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public Iterator<T> iterator() {
return new Iterator<>() {
private int index;
@Override
public boolean hasNext() {
return index < size;
}
@Override
public T next() {
if (index < size) {
return elementData[index++];
}
throw new NoSuchElementException("No more elements in FastList");
}
};
}
/**
* {@inheritDoc}
*/
@Override
public Object[] toArray() {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public <E> E[] toArray(E[] a) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public boolean addAll(Collection<? extends T> c) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public boolean addAll(int index, Collection<? extends T> c) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public void add(int index, T element) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public int indexOf(Object o) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public int lastIndexOf(Object o) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public ListIterator<T> listIterator() {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public ListIterator<T> listIterator(int index) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public List<T> subList(int fromIndex, int toIndex) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public Object clone() {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public void forEach(Consumer<? super T> action) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public Spliterator<T> spliterator() {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public boolean removeIf(Predicate<? super T> filter) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public void replaceAll(UnaryOperator<T> operator) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public void sort(Comparator<? super T> c) {
throw new UnsupportedOperationException();
}
}

@ -0,0 +1,77 @@
package org.xbib.jdbc.connection.pool.util;
import java.util.concurrent.TimeUnit;
public class NanosecondClockSource implements ClockSource {
/**
* {@inheritDoc}
*/
@Override
public long currentTime0() {
return System.nanoTime();
}
/**
* {@inheritDoc}
*/
@Override
public long toMillis0(final long time) {
return TimeUnit.NANOSECONDS.toMillis(time);
}
/**
* {@inheritDoc}
*/
@Override
public long toNanos0(final long time) {
return time;
}
/**
* {@inheritDoc}
*/
@Override
public long elapsedMillis0(final long startTime) {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
}
/**
* {@inheritDoc}
*/
@Override
public long elapsedMillis0(final long startTime, final long endTime) {
return TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
}
/**
* {@inheritDoc}
*/
@Override
public long elapsedNanos0(final long startTime) {
return System.nanoTime() - startTime;
}
/**
* {@inheritDoc}
*/
@Override
public long elapsedNanos0(final long startTime, final long endTime) {
return endTime - startTime;
}
/**
* {@inheritDoc}
*/
@Override
public long plusMillis0(final long time, final long millis) {
return time + TimeUnit.MILLISECONDS.toNanos(millis);
}
/**
* {@inheritDoc}
*/
@Override
public TimeUnit getSourceTimeUnit0() {
return TimeUnit.NANOSECONDS;
}
}

@ -0,0 +1,78 @@
package org.xbib.io.pool.jdbc;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.jdbc.connection.pool.util.Bag;
import java.util.concurrent.CompletableFuture;
import org.xbib.jdbc.connection.pool.Pool;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
import org.xbib.jdbc.connection.pool.PoolEntry;
@ExtendWith(PoolTestExtension.class)
public class BagTest {
private static PoolDataSource ds;
private static Pool pool;
@BeforeAll
public static void setup() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(2);
config.setInitializationFailTimeout(0);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
ds = new PoolDataSource(config);
pool = ds.getPool();
}
@AfterAll
public static void teardown()
{
ds.close();
}
@Test
public void testBag() throws Exception {
try (Bag<PoolEntry> bag = new Bag<>((x) -> CompletableFuture.completedFuture(Boolean.TRUE))) {
assertEquals(0, bag.values(8).size());
PoolEntry reserved = pool.newPoolEntry();
bag.add(reserved);
bag.reserve(reserved);
PoolEntry inuse = pool.newPoolEntry();
bag.add(inuse);
bag.borrow(2, MILLISECONDS);
PoolEntry notinuse = pool.newPoolEntry();
bag.add(notinuse);
bag.dumpState();
bag.requite(reserved);
bag.remove(notinuse);
assertTrue(bag.getLastMessage().contains("not borrowed or reserved"));
bag.unreserve(notinuse);
assertTrue(bag.getLastMessage().contains("was not reserved"));
bag.remove(inuse);
bag.remove(inuse);
assertTrue(bag.getLastMessage().contains("not borrowed or reserved"));
bag.close();
try {
PoolEntry bagEntry = pool.newPoolEntry();
bag.add(bagEntry);
assertNotEquals(bagEntry, bag.borrow(100, MILLISECONDS));
}
catch (IllegalStateException e) {
assertTrue(bag.getLastMessage().contains("ignoring add()"));
}
assertNotNull(notinuse.toString());
}
}
}

@ -0,0 +1,39 @@
package org.xbib.io.pool.jdbc;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
public class ConcurrentCloseConnectionTest
{
@Test
public void testConcurrentClose() throws Exception
{
PoolConfig config = new PoolConfig();
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config);
final Connection connection = ds.getConnection()) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 500; i++) {
final PreparedStatement preparedStatement = connection.prepareStatement("");
futures.add(executorService.submit((Callable<Void>) () -> {
preparedStatement.close();
return null;
}));
}
executorService.shutdown();
for (Future<?> future : futures) {
future.get();
}
}
}
}

@ -0,0 +1,69 @@
package org.xbib.io.pool.jdbc;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;
import java.sql.Connection;
import java.sql.SQLException;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.stubbing.Answer;
import org.xbib.io.pool.jdbc.mock.MockDataSource;
import org.xbib.jdbc.connection.pool.util.ClockSource;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
/**
* Test for cases when db network connectivity goes down and close is called on
* existing connections. By default we block longer than getMaximumTimeout
* (it can hang for a lot of time depending on driver timeout settings).
* Closing the connections asynchronously fixes this issue.
*/
@ExtendWith(PoolTestExtension.class)
public class ConnectionCloseBlockingTest {
private static volatile boolean shouldFail = false;
@Disabled
@Test
public void testConnectionCloseBlocking() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(1500);
config.setDataSource(new CustomMockDataSource());
long start = ClockSource.currentTime();
try (PoolDataSource ds = new PoolDataSource(config);
Connection connection = ds.getConnection()) {
connection.close();
PoolTestExtension.quietlySleep(1100L);
shouldFail = true;
try (Connection connection2 = ds.getConnection()) {
assertTrue((ClockSource.elapsedMillis(start) < config.getConnectionTimeout()),
"waited longer than timeout: " + config.getConnectionTimeout());
}
} catch (SQLException e) {
assertTrue((ClockSource.elapsedMillis(start) < config.getConnectionTimeout()),
"getConnection failed because close connection took longer than timeout");
}
}
private static class CustomMockDataSource extends MockDataSource {
@Override
public Connection getConnection() throws SQLException {
Connection mockConnection = super.getConnection();
when(mockConnection.isValid(anyInt())).thenReturn(!shouldFail);
doAnswer((Answer<Void>) invocation -> {
if (shouldFail) {
SECONDS.sleep(2);
}
return null;
}).when(mockConnection).close();
return mockConnection;
}
}
}

@ -0,0 +1,141 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.xbib.jdbc.connection.pool.util.ClockSource.currentTime;
import static org.xbib.jdbc.connection.pool.util.ClockSource.elapsedMillis;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.io.pool.jdbc.mock.StubDataSource;
import java.sql.Connection;
import java.text.MessageFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
import org.xbib.jdbc.connection.pool.Pool;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
@ExtendWith(PoolTestExtension.class)
public class ConnectionPoolSizeVsThreadsTest {
private static final Logger LOGGER = Logger.getLogger(ConnectionPoolSizeVsThreadsTest.class.getName());
private static final int ITERATIONS = 50_000;
@Test
public void testPoolSizeAboutSameSizeAsThreadCount() throws Exception {
final int threadCount = 50;
final Counts counts = testPoolSize(2, 100,
threadCount, 1, 0, 20,
ITERATIONS, TimeUnit.SECONDS.toMillis(2));
assertEquals(threadCount, counts.maxActive, 15);
assertEquals(threadCount, counts.maxTotal, 5);
}
@Test
public void testSlowConnectionTimeBurstyWork() throws Exception {
final int threadCount = 50;
final int workItems = threadCount * 100;
final int workTimeMs = 0;
final int connectionAcquisitionTimeMs = 250;
final Counts counts = testPoolSize(2, 100,
threadCount, workTimeMs, 0, connectionAcquisitionTimeMs,
workItems, TimeUnit.SECONDS.toMillis(3));
final long totalWorkTime = workItems * workTimeMs;
final long connectionMax = totalWorkTime / connectionAcquisitionTimeMs;
assertTrue(connectionMax <= counts.maxActive);
assertEquals(connectionMax, counts.maxTotal, 2 + 2);
}
private Counts testPoolSize(final int minIdle,
final int maxPoolSize,
final int threadCount,
final long workTimeMs,
final long restTimeMs,
final long connectionAcquisitionTimeMs,
final int iterations,
final long postTestTimeMs) throws Exception {
LOGGER.info(MessageFormat.format("Starting test (minIdle={0}, maxPoolSize={1}, threadCount={2}, workTimeMs={3}, restTimeMs={4}, connectionAcquisitionTimeMs={5}, iterations={6}, postTestTimeMs={7})",
minIdle, maxPoolSize, threadCount, workTimeMs, restTimeMs, connectionAcquisitionTimeMs, iterations, postTestTimeMs));
final PoolConfig config = new PoolConfig();
config.setMinimumIdle(minIdle);
config.setMaximumPoolSize(maxPoolSize);
config.setInitializationFailTimeout(Long.MAX_VALUE);
config.setConnectionTimeout(2500);
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
final AtomicReference<Exception> ref = new AtomicReference<>(null);
try (final PoolDataSource ds = new PoolDataSource(config)) {
final StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
// connection acquisition takes more than 0 ms in a real system
stubDataSource.setConnectionAcquistionTime(connectionAcquisitionTimeMs);
final ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
final CountDownLatch allThreadsDone = new CountDownLatch(iterations);
for (int i = 0; i < iterations; i++) {
threadPool.submit(() -> {
if (ref.get() == null) {
PoolTestExtension.quietlySleep(restTimeMs);
try (Connection c2 = ds.getConnection()) {
PoolTestExtension.quietlySleep(workTimeMs);
}
catch (Exception e) {
ref.set(e);
}
}
allThreadsDone.countDown();
});
}
final Pool pool = ds.getPool();
final Counts underLoad = new Counts();
while (allThreadsDone.getCount() > 0 || pool.getTotalConnections() < minIdle) {
PoolTestExtension.quietlySleep(50);
underLoad.updateMaxCounts(pool);
}
LOGGER.info(MessageFormat.format("test over, waiting for post delay time {0} ms ", postTestTimeMs));
PoolTestExtension.quietlySleep(connectionAcquisitionTimeMs + workTimeMs + restTimeMs);
final Counts postLoad = new Counts();
final long start = currentTime();
while (elapsedMillis(start) < postTestTimeMs) {
PoolTestExtension.quietlySleep(50);
postLoad.updateMaxCounts(pool);
}
allThreadsDone.await();
threadPool.shutdown();
threadPool.awaitTermination(30, TimeUnit.SECONDS);
if (ref.get() != null) {
LOGGER.severe("task failed: " + ref.get());
fail("task failed");
}
LOGGER.info("under load... " + underLoad);
LOGGER.info("post load.... " + postLoad);
if (postTestTimeMs > 0) {
if (postLoad.maxActive != 0) {
fail("max active was greater than 0 after test was done");
}
final int createdAfterWorkAllFinished = postLoad.maxTotal - underLoad.maxTotal;
assertEquals(0, createdAfterWorkAllFinished, 1,
"connections were created when there was no waiting consumers");
}
return underLoad;
}
}
private static class Counts {
int maxTotal = 0;
int maxActive = 0;
void updateMaxCounts(Pool pool) {
maxTotal = Math.max(pool.getTotalConnections(), maxTotal);
maxActive = Math.max(pool.getActiveConnections(), maxActive);
}
@Override
public String toString() {
return "counts{" + "max total=" + maxTotal + ", max active=" + maxActive + '}';
}
}
}

@ -0,0 +1,59 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
public class ConnectionRaceConditionTest {
private static final Logger logger = Logger.getLogger(ConnectionRaceConditionTest.class.getName());
public static final int ITERATIONS = 10_000;
@Test
public void testRaceCondition() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(10);
config.setInitializationFailTimeout(Long.MAX_VALUE);
config.setConnectionTimeout(5000);
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
final AtomicReference<Exception> ref = new AtomicReference<>(null);
try (final PoolDataSource ds = new PoolDataSource(config)) {
ExecutorService threadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < ITERATIONS; i++) {
threadPool.submit(new Callable<Exception>() {
@Override
public Exception call() throws Exception {
if (ref.get() == null) {
Connection c2;
try {
c2 = ds.getConnection();
ds.evictConnection(c2);
} catch (Exception e) {
ref.set(e);
}
}
return null;
}
});
}
threadPool.shutdown();
threadPool.awaitTermination(30, TimeUnit.SECONDS);
if (ref.get() != null) {
logger.severe("task failed: " + ref.get());
fail("task failed");
}
} catch (Exception e) {
throw e;
}
}
}

@ -0,0 +1,134 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
import org.xbib.jdbc.connection.pool.ProxyConnection;
public class ConnectionStateTest {
@Test
public void testAutoCommit() throws Exception {
Properties properties = new Properties();
properties.put("user", "bar");
properties.put("password", "secret");
properties.put("url", "baf");
properties.put("loginTimeout", "10");
PoolConfig config = new PoolConfig(properties);
config.setAutoCommit(true);
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection connection = ds.getConnection()) {
Connection unwrap = connection.unwrap(Connection.class);
unwrap.setAutoCommit(false);
connection.close();
assertFalse(unwrap.getAutoCommit());
}
}
}
@Test
public void testTransactionIsolation() throws Exception {
Properties properties = new Properties();
PoolConfig config = new PoolConfig(properties);
config.setTransactionIsolation("TRANSACTION_READ_COMMITTED");
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection connection = ds.getConnection()) {
Connection unwrap = connection.unwrap(Connection.class);
unwrap.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
connection.close();
assertEquals(Connection.TRANSACTION_READ_UNCOMMITTED, unwrap.getTransactionIsolation());
}
}
}
@Test
public void testIsolation() {
PoolConfig config = new PoolConfig();
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
config.setTransactionIsolation("TRANSACTION_REPEATABLE_READ");
config.validate();
int transactionIsolation = PoolTestExtension.getTransactionIsolation(config.getTransactionIsolation());
assertSame(Connection.TRANSACTION_REPEATABLE_READ, transactionIsolation);
}
@Test
public void testReadOnly() throws Exception {
PoolConfig config = new PoolConfig();
config.setCatalog("test");
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection connection = ds.getConnection()) {
Connection unwrap = connection.unwrap(Connection.class);
connection.setReadOnly(true);
connection.close();
assertFalse(unwrap.isReadOnly());
}
}
}
@Test
public void testCatalog() throws Exception {
PoolConfig config = new PoolConfig();
config.setCatalog("test");
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection connection = ds.getConnection()) {
Connection unwrap = connection.unwrap(Connection.class);
connection.setCatalog("other");
connection.close();
assertEquals("test", unwrap.getCatalog());
}
}
}
@Test
public void testCommitTracking() throws Exception {
PoolConfig config = new PoolConfig();
config.setAutoCommit(false);
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection connection = ds.getConnection()) {
Statement statement = connection.createStatement();
statement.execute("SELECT something");
assertTrue(((ProxyConnection)connection).isCommitStateDirty());
connection.commit();
assertFalse(((ProxyConnection)connection).isCommitStateDirty());
statement.execute("SELECT something", Statement.NO_GENERATED_KEYS);
assertTrue(((ProxyConnection)connection).isCommitStateDirty());
connection.rollback();
assertFalse(((ProxyConnection)connection).isCommitStateDirty());
ResultSet resultSet = statement.executeQuery("SELECT something");
assertTrue(((ProxyConnection)connection).isCommitStateDirty());
connection.rollback(null);
assertFalse(((ProxyConnection)connection).isCommitStateDirty());
resultSet.updateRow();
assertTrue(((ProxyConnection)connection).isCommitStateDirty());
}
}
}
}

@ -0,0 +1,552 @@
package org.xbib.io.pool.jdbc;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.io.pool.jdbc.mock.StubConnection;
import org.xbib.io.pool.jdbc.mock.StubDataSource;
import org.xbib.io.pool.jdbc.mock.StubStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLTransientConnectionException;
import java.sql.Statement;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.xbib.jdbc.connection.pool.Pool;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
import org.xbib.jdbc.connection.pool.PoolInitializationException;
@ExtendWith(PoolTestExtension.class)
public class ConnectionTest {
@Test
public void testCreate() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1");
config.setConnectionInitSql("SELECT 1");
config.setReadOnly(true);
config.setConnectionTimeout(2500);
config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(30));
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
ds.setLoginTimeout(10);
assertSame(10, ds.getLoginTimeout());
Pool pool = ds.getPool();
ds.getConnection().close();
assertSame(1, pool.getTotalConnections(), "total connections not as expected");
assertSame(1, pool.getIdleConnections(), "idle connections not as expected");
try (Connection connection = ds.getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT * FROM device WHERE device_id=?")) {
assertNotNull(connection);
assertNotNull(statement);
assertSame(1, pool.getTotalConnections(), "total connections not as expected");
assertSame(0, pool.getIdleConnections(), "idle connections not as expected");
statement.setInt(1, 0);
try (ResultSet resultSet = statement.executeQuery()) {
assertNotNull(resultSet);
assertFalse(resultSet.next());
}
}
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
assertSame(1, pool.getIdleConnections(), "idle connections not as expected");
}
}
@Test
public void testMaxLifetime() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(1500);
config.setConnectionTestQuery("VALUES 1");
config.setInitializationFailTimeout(Long.MAX_VALUE);
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
config.setHousekeepingPeriodMs(2000L);
config.setMaxLifetime(30000L); // must be 30s or higher
try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool();
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
assertSame(1, pool.getIdleConnections(), "Idle connections not as expected");
Connection unwrap;
Connection unwrap2;
try (Connection connection = ds.getConnection()) {
unwrap = connection.unwrap(Connection.class);
assertNotNull(connection);
assertSame(1, pool.getTotalConnections(), "Second total connections not as expected");
assertSame(0, pool.getIdleConnections(), "Second idle connections not as expected");
}
assertSame(1, pool.getIdleConnections(), "Idle connections not as expected");
try (Connection connection = ds.getConnection()) {
unwrap2 = connection.unwrap(Connection.class);
assertSame(unwrap, unwrap2);
assertSame(1, pool.getTotalConnections(), "Second total connections not as expected");
assertSame(0, pool.getIdleConnections(), "Second idle connections not as expected");
//pool.evictConnection(connection);
}
Thread.sleep(31000L);
try (Connection connection = ds.getConnection()) {
unwrap2 = connection.unwrap(Connection.class);
assertNotSame(unwrap, unwrap2, "Expected a different connection");
}
assertSame(1, pool.getTotalConnections(), "Post total connections not as expected");
assertSame(1, pool.getIdleConnections(), "Post idle connections not as expected");
}
}
@Test
public void testMaxLifetime2() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(2500);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
config.setHousekeepingPeriodMs(100L);
try (PoolDataSource ds = new PoolDataSource(config)) {
ds.getPool().getConfig().setMaxLifetime(700);
Pool pool = ds.getPool();
assertSame(0, pool.getTotalConnections(), "Total connections not as expected");
assertSame(0, pool.getIdleConnections(), "Idle connections not as expected");
Connection unwrap;
Connection unwrap2;
try (Connection connection = ds.getConnection()) {
unwrap = connection.unwrap(Connection.class);
assertNotNull(connection);
assertSame(1, pool.getTotalConnections(), "Second total connections not as expected");
assertSame(0, pool.getIdleConnections(), "Second idle connections not as expected");
}
assertSame(1, pool.getIdleConnections(), "Idle connections not as expected");
try (Connection connection = ds.getConnection()) {
unwrap2 = connection.unwrap(Connection.class);
assertSame(unwrap, unwrap2);
assertSame(1, pool.getTotalConnections(), "Second total connections not as expected");
assertSame( 0, pool.getIdleConnections(), "Second idle connections not as expected");
}
PoolTestExtension.quietlySleep(800);
try (Connection connection = ds.getConnection()) {
unwrap2 = connection.unwrap(Connection.class);
assertNotSame(unwrap, unwrap2, "Expected a different connection");
}
assertSame( 1, pool.getTotalConnections(), "Post total connections not as expected");
assertSame(1, pool.getIdleConnections(), "Post idle connections not as expected");
}
}
@Test
public void testDoubleClose() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(2500);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config);
Connection connection = ds.getConnection()) {
connection.close();
connection.abort(null);
assertTrue(connection.isClosed(), "Connection should have closed");
assertFalse(connection.isValid(5), "Connection should have closed");
assertTrue(connection.toString().contains("ProxyConnection"), "Expected to contain ProxyConnection, but was " + connection);
}
}
@Test
public void testEviction() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(5);
config.setConnectionTimeout(2500);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
Connection connection = ds.getConnection();
Pool pool = ds.getPool();
assertEquals(1, pool.getTotalConnections());
ds.evictConnection(connection);
assertEquals(0, pool.getTotalConnections());
}
}
@Test
public void testEviction2() throws Exception {
PoolConfig config = new PoolConfig();
config.setMaximumPoolSize(5);
config.setConnectionTimeout(2500);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
//config.setExceptionOverrideClassName(OverrideHandler.class.getName());
try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool();
while (pool.getTotalConnections() < 5) {
PoolTestExtension.quietlySleep(100L);
}
try (Connection connection = ds.getConnection()) {
assertNotNull(connection);
PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?");
assertNotNull(statement);
ResultSet resultSet = statement.executeQuery();
assertNotNull(resultSet);
try {
statement.getMaxFieldSize();
} catch (Exception e) {
assertSame(SQLException.class, e.getClass());
}
}
assertEquals(5, pool.getTotalConnections(), "Total connections not as expected");
assertEquals(5, pool.getIdleConnections(), "Idle connections not as expected");
}
}
@Test
public void testEviction3() throws Exception {
PoolConfig config = new PoolConfig();
config.setMaximumPoolSize(5);
config.setConnectionTimeout(2500);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool();
while (pool.getTotalConnections() < 5) {
PoolTestExtension.quietlySleep(100L);
}
try (Connection connection = ds.getConnection()) {
assertNotNull(connection);
PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?");
assertNotNull(statement);
ResultSet resultSet = statement.executeQuery();
assertNotNull(resultSet);
try {
statement.getMaxFieldSize();
} catch (Exception e) {
assertSame(SQLException.class, e.getClass());
}
}
assertEquals(5, pool.getTotalConnections(), "Total connections not as expected");
assertEquals(5, pool.getIdleConnections(), "Idle connections not as expected");
}
}
@Test
public void testEvictAllRefill() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(5);
config.setMaximumPoolSize(10);
config.setConnectionTimeout(2500);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
config.setHousekeepingPeriodMs(100L);
try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool();
for (int i = 0; i < 5; i++) {
final Connection conn = ds.getConnection();
ds.evictConnection(conn);
}
PoolTestExtension.quietlySleep(SECONDS.toMillis(2));
int count = 0;
while (pool.getIdleConnections() < 5 && count++ < 20) {
PoolTestExtension.quietlySleep(100);
}
assertEquals(5, pool.getIdleConnections(),
"after eviction, refill did not reach expected 5 connections");
}
}
@Test
public void testBackfill() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(4);
config.setConnectionTimeout(1000);
config.setInitializationFailTimeout(Long.MAX_VALUE);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
StubConnection.slowCreate = true;
try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool();
PoolTestExtension.quietlySleep(1250);
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
assertSame(1, pool.getIdleConnections(), "Idle connections not as expected");
try (Connection connection = ds.getConnection()) {
assertNotNull(connection);
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
assertSame(0, pool.getIdleConnections(), "Idle connections not as expected");
PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?");
assertNotNull(statement);
ResultSet resultSet = statement.executeQuery();
assertNotNull(resultSet);
try {
statement.getMaxFieldSize();
fail();
} catch (Exception e) {
assertSame(SQLException.class, e.getClass());
}
pool.logPoolState("testBackfill() before close...");
}
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
pool.logPoolState("testBackfill() after close...");
PoolTestExtension.quietlySleep(1250);
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
assertSame( 1, pool.getIdleConnections(), "Idle connections not as expected");
} finally {
StubConnection.slowCreate = false;
}
}
@Test
public void testMaximumPoolLimit() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(4);
config.setConnectionTimeout(20000);
config.setInitializationFailTimeout(0);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
final AtomicReference<Exception> ref = new AtomicReference<>();
StubConnection.count.set(0); // reset counter
try (final PoolDataSource ds = new PoolDataSource(config)) {
final Pool pool = ds.getPool();
Thread[] threads = new Thread[20];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
try {
pool.logPoolState("Before acquire ");
try (Connection ignored = ds.getConnection()) {
pool.logPoolState("After acquire ");
PoolTestExtension.quietlySleep(500);
}
} catch (Exception e) {
ref.set(e);
}
});
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
pool.logPoolState("before check ");
assertNull(ref.get(), (ref.get() != null ? ref.get().toString() : ""));
assertSame(4, StubConnection.count.get(), "StubConnection count not as expected");
}
}
@Test
public void testOldDriver() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(2500);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
StubConnection.oldDriver = true;
StubStatement.oldDriver = true;
try (PoolDataSource ds = new PoolDataSource(config)) {
PoolTestExtension.quietlySleep(500);
try (Connection ignored = ds.getConnection()) {
// close
}
PoolTestExtension.quietlySleep(500);
try (Connection ignored = ds.getConnection()) {
// close
}
} finally {
StubConnection.oldDriver = false;
StubStatement.oldDriver = false;
}
}
@Test
public void testInitializationFailure1() throws Exception {
StubDataSource stubDataSource = new StubDataSource();
stubDataSource.setThrowException(new SQLException("Connection refused"));
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(2500);
config.setConnectionTestQuery("VALUES 1");
config.setDataSource(stubDataSource);
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection ignored = ds.getConnection()) {
fail("Initialization should have failed");
} catch (SQLException e) {
// passed
}
}
}
@Test
public void testInitializationFailure2() throws Exception {
StubDataSource stubDataSource = new StubDataSource();
stubDataSource.setThrowException(new SQLException("Connection refused"));
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setConnectionTestQuery("VALUES 1");
config.setDataSource(stubDataSource);
try (PoolDataSource ds = new PoolDataSource(config);
Connection ignored = ds.getConnection()) {
fail("Initialization should have failed");
} catch (SQLTransientConnectionException e) {
// passed
}
}
@Test
public void testInvalidConnectionTestQuery() throws Exception {
class BadConnection extends StubConnection {
@Override
public Statement createStatement() throws SQLException {
throw new SQLException("Simulated exception in createStatement()");
}
}
StubDataSource stubDataSource = new StubDataSource() {
@Override
public Connection getConnection() {
return new BadConnection();
}
};
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(2);
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3));
config.setConnectionTestQuery("VALUES 1");
config.setInitializationFailTimeout(TimeUnit.SECONDS.toMillis(2));
config.setDataSource(stubDataSource);
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection ignored = ds.getConnection()) {
fail("getConnection() should have failed");
} catch (SQLException e) {
assertSame("Simulated exception in createStatement()", e.getNextException().getMessage());
}
} catch (PoolInitializationException e) {
assertSame("Simulated exception in createStatement()", e.getCause().getMessage());
}
config.setInitializationFailTimeout(0);
try (PoolDataSource ignored = new PoolDataSource(config)) {
fail("Initialization should have failed");
} catch (PoolInitializationException e) {
// passed
}
}
@Test
public void testDataSourceRaisesErrorWhileInitializationTestQuery() throws Exception {
StubDataSourceWithErrorSwitch stubDataSource = new StubDataSourceWithErrorSwitch();
stubDataSource.setErrorOnConnection(true);
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setConnectionTestQuery("VALUES 1");
config.setDataSource(stubDataSource);
try (PoolDataSource ds = new PoolDataSource(config);
Connection ignored = ds.getConnection()) {
fail("Initialization should have failed");
} catch (SQLTransientConnectionException e) {
// passed
}
}
@Disabled
@Test
public void testDataSourceRaisesErrorAfterInitializationTestQuery() throws Exception {
StubDataSourceWithErrorSwitch stubDataSource = new StubDataSourceWithErrorSwitch();
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(2);
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3));
config.setConnectionTestQuery("VALUES 1");
config.setInitializationFailTimeout(TimeUnit.SECONDS.toMillis(2));
config.setDataSource(stubDataSource);
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection ignored = ds.getConnection()) {
stubDataSource.setErrorOnConnection(true);
fail("SQLException should occur!");
} catch (Exception e) {
// request will get timed-out
assertTrue(e.getMessage().contains("request timed out"));
}
}
}
@Test
public void testPopulationSlowAcquisition() throws Exception {
PoolConfig config = new PoolConfig();
config.setMaximumPoolSize(20);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
config.setHousekeepingPeriodMs(1000L);
StubConnection.slowCreate = true;
try (PoolDataSource ds = new PoolDataSource(config)) {
ds.getPool().getConfig().setIdleTimeout(3000);
SECONDS.sleep(2);
Pool pool = ds.getPool();
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
assertSame(1, pool.getIdleConnections(), "Idle connections not as expected");
try (Connection connection = ds.getConnection()) {
assertNotNull(connection);
SECONDS.sleep(20);
assertSame(20, pool.getTotalConnections(), "Second total connections not as expected");
assertSame(19, pool.getIdleConnections(), "Second idle connections not as expected");
}
assertSame(20, pool.getIdleConnections(), "Idle connections not as expected");
SECONDS.sleep(5);
assertSame(20, pool.getTotalConnections(), "Third total connections not as expected");
assertSame(20, pool.getIdleConnections(), "Third idle connections not as expected");
} finally {
StubConnection.slowCreate = false;
}
}
@Test
public void testMinimumIdleZero() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(5);
config.setConnectionTimeout(1000L);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config);
Connection ignored = ds.getConnection()) {
// passed
} catch (SQLTransientConnectionException sqle) {
fail("Failed to obtain connection");
}
}
private static class StubDataSourceWithErrorSwitch extends StubDataSource {
private boolean errorOnConnection = false;
@Override
public Connection getConnection() {
if (!errorOnConnection) {
return new StubConnection();
}
throw new RuntimeException("bad thing happens on datasource");
}
public void setErrorOnConnection(boolean errorOnConnection) {
this.errorOnConnection = errorOnConnection;
}
}
/*public static class OverrideHandler implements SQLExceptionOverride {
@java.lang.Override
public Override adjudicate(SQLException sqlException) {
return (sqlException.getSQLState().equals("08999")) ? Override.DO_NOT_EVICT : Override.CONTINUE_EVICT;
}
}*/
}

@ -0,0 +1,178 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
import org.xbib.io.pool.jdbc.mock.StubConnection;
import org.xbib.io.pool.jdbc.mock.StubDataSource;
import org.xbib.jdbc.connection.pool.util.ClockSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.xbib.jdbc.connection.pool.Pool;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
public class ConnectionTimeoutRetryTest {
@Test
public void testConnectionRetries() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(2800);
config.setValidationTimeout(2800);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
stubDataSource.setThrowException(new SQLException("Connection refused"));
long start = ClockSource.currentTime();
try (Connection connection = ds.getConnection()) {
connection.close();
fail("Should not have been able to get a connection.");
} catch (SQLException e) {
long elapsed = ClockSource.elapsedMillis(start);
long timeout = config.getConnectionTimeout();
assertTrue(elapsed >= timeout, "Didn't wait long enough for timeout");
}
}
}
@Test
public void testConnectionRetries2() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(2800);
config.setValidationTimeout(2800);
config.setInitializationFailTimeout(0);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
final StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
stubDataSource.setThrowException(new SQLException("Connection refused"));
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() -> stubDataSource.setThrowException(null), 300, TimeUnit.MILLISECONDS);
long start = ClockSource.currentTime();
try {
try (Connection connection = ds.getConnection()) {
// close immediately
}
long elapsed = ClockSource.elapsedMillis(start);
assertTrue(elapsed < config.getConnectionTimeout(), "waited too long to get a connection");
} catch (SQLException e) {
fail("Should not have timed out: " + e.getMessage());
} finally {
scheduler.shutdownNow();
}
}
}
@Test
public void testConnectionRetries3() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(2);
config.setConnectionTimeout(2800);
config.setValidationTimeout(2800);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
final Connection connection1 = ds.getConnection();
final Connection connection2 = ds.getConnection();
assertNotNull(connection1);
assertNotNull(connection2);
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
scheduler.schedule(() -> {
try {
connection1.close();
} catch (Exception e) {
e.printStackTrace(System.err);
}
}, 800, TimeUnit.MILLISECONDS);
long start = ClockSource.currentTime();
try {
try (Connection connection3 = ds.getConnection()) {
// close immediately
}
long elapsed = ClockSource.elapsedMillis(start);
assertTrue((elapsed >= 700) && (elapsed < 950), "waited too long to get a connection");
} catch (SQLException e) {
fail("Should not have timed out.");
} finally {
scheduler.shutdownNow();
}
}
}
@Test
public void testConnectionRetries5() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(2);
config.setConnectionTimeout(1000);
config.setValidationTimeout(1000);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
final Connection connection1 = ds.getConnection();
long start = ClockSource.currentTime();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
scheduler.schedule(() -> {
try {
connection1.close();
} catch (Exception e) {
e.printStackTrace(System.err);
}
}, 250, TimeUnit.MILLISECONDS);
StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
stubDataSource.setThrowException(new SQLException("Connection refused"));
try {
try (Connection connection2 = ds.getConnection()) {
// close immediately
}
long elapsed = ClockSource.elapsedMillis(start);
assertTrue((elapsed >= 250) && (elapsed < config.getConnectionTimeout()), "Waited too long to get a connection");
} catch (SQLException e) {
fail("Should not have timed out.");
} finally {
scheduler.shutdownNow();
}
}
}
@Test
public void testConnectionIdleFill() throws Exception {
StubConnection.slowCreate = false;
PoolConfig config = new PoolConfig();
config.setMinimumIdle(5);
config.setMaximumPoolSize(10);
config.setConnectionTimeout(2000);
config.setValidationTimeout(2000);
config.setConnectionTestQuery("VALUES 2");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
config.setHousekeepingPeriodMs(400);
try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool();
try (Connection connection1 = ds.getConnection();
Connection connection2 = ds.getConnection();
Connection connection3 = ds.getConnection();
Connection connection4 = ds.getConnection();
Connection connection5 = ds.getConnection();
Connection connection6 = ds.getConnection();
Connection connection7 = ds.getConnection()) {
Thread.sleep(1300);
assertSame(10, pool.getTotalConnections(), "Total connections not as expected");
assertSame(3, pool.getIdleConnections(), "Idle connections not as expected");
}
assertSame(10, pool.getTotalConnections(), "Total connections not as expected");
assertSame(10, pool.getIdleConnections(), "Idle connections not as expected");
}
}
}

@ -0,0 +1,57 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.xbib.jdbc.connection.pool.util.DefaultThreadFactory;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
public class HouseKeeperCleanupTest {
private ScheduledThreadPoolExecutor executor;
@BeforeEach
public void before() throws Exception {
ThreadFactory threadFactory = new DefaultThreadFactory("global-housekeeper", true);
executor = new ScheduledThreadPoolExecutor(1, threadFactory,
new ThreadPoolExecutor.DiscardPolicy());
executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
executor.setRemoveOnCancelPolicy(true);
}
@Test
public void testHouseKeeperCleanupWithCustomExecutor() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(10);
config.setInitializationFailTimeout(Long.MAX_VALUE);
config.setConnectionTimeout(2500);
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
config.setScheduledExecutor(executor);
PoolConfig config2 = new PoolConfig();
config2.setMinimumIdle(0);
config2.setMaximumPoolSize(10);
config2.setInitializationFailTimeout(Long.MAX_VALUE);
config2.setConnectionTimeout(2500);
config2.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
config2.setScheduledExecutor(executor);
try (
final PoolDataSource ds1 = new PoolDataSource(config);
final PoolDataSource ds2 = new PoolDataSource(config2)) {
assertEquals(4, executor.getQueue().size(), "scheduled tasks count not as expected");
}
assertEquals( 0, executor.getQueue().size(), "scheduled tasks count not as expected");
}
@AfterEach
public void after() throws Exception {
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
}
}

@ -0,0 +1,48 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
public class IsolationTest {
@Test
public void testIsolation() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setIsolateInternalQueries(true);
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection connection = ds.getConnection()) {
connection.close();
try (Connection connection2 = ds.getConnection()) {
connection2.close();
assertNotSame(connection, connection2);
assertSame(connection.unwrap(Connection.class), connection2.unwrap(Connection.class));
}
}
}
}
@Test
public void testNonIsolation() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setIsolateInternalQueries(false);
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection connection = ds.getConnection()) {
connection.close();
try (Connection connection2 = ds.getConnection()) {
connection2.close();
assertSame(connection.unwrap(Connection.class), connection2.unwrap(Connection.class));
}
}
}
}
}

@ -0,0 +1,59 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.xbib.jdbc.connection.pool.util.DriverDataSource;
import java.sql.Connection;
import java.util.Properties;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
public class JdbcDriverTest {
private PoolDataSource ds;
@AfterEach
public void teardown() {
if (ds != null) {
ds.close();
}
}
@Test
public void driverTest1() throws Exception {
Properties properties = new Properties();
properties.put("url", "jdbc:stub");
properties.put("user", "bart");
properties.put("password", "simpson");
PoolConfig config = new PoolConfig(properties);
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1");
config.setDriverClassName("org.xbib.io.pool.jdbc.mock.StubDriver");
ds = new PoolDataSource(config);
assertTrue(ds.isWrapperFor(DriverDataSource.class));
DriverDataSource unwrap = ds.unwrap(DriverDataSource.class);
assertNotNull(unwrap);
try (Connection connection = ds.getConnection()) {
// test that getConnection() succeeds
}
}
@Test
public void driverTest2() throws Exception {
Properties properties = new Properties();
properties.put("url", "jdbc:invalid");
PoolConfig config = new PoolConfig(properties);
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1");
config.setDriverClassName("org.xbib.io.pool.jdbc.mock.StubDriver");
try {
ds = new PoolDataSource(config);
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("claims to not accept"));
}
}
}

@ -0,0 +1,107 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xbib.jdbc.connection.pool.Pool;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
@ExtendWith(PoolTestExtension.class)
public class PoolTest {
private static final Logger logger = Logger.getLogger(PoolTest.class.getName());
@BeforeAll
static void setup() throws Exception {
Properties properties = new Properties();
properties.put("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
PoolConfig config = new PoolConfig(properties);
config.setMinimumIdle(1);
config.setMaximumPoolSize(2);
config.setConnectionTestQuery("SELECT 1");
config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
try (PoolDataSource ds = new PoolDataSource(config);
Connection conn = ds.getConnection();
Statement stmt = conn.createStatement()) {
stmt.executeUpdate("DROP TABLE IF EXISTS basic_pool_test");
stmt.executeUpdate("CREATE TABLE basic_pool_test ("
+ "id INTEGER NOT NULL IDENTITY PRIMARY KEY, "
+ "timestamp TIMESTAMP, "
+ "string VARCHAR(128), "
+ "string_from_number NUMERIC "
+ ")");
}
}
@Test
public void testIdleTimeout() throws Exception {
Properties properties = new Properties();
properties.put("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
PoolConfig config = new PoolConfig(properties);
config.setMinimumIdle(5);
config.setMaximumPoolSize(10);
config.setConnectionTestQuery("SELECT 1");
config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
System.setProperty("pool.jdbc.housekeeping.periodMs", "1000");
try (PoolDataSource ds = new PoolDataSource(config)) {
System.clearProperty("pool.jdbc.housekeeping.periodMs");
TimeUnit.SECONDS.sleep(1);
Pool pool = ds.getPool();
config.setIdleTimeout(3000);
assertEquals(5, pool.getTotalConnections(), "Total connections not as expected");
assertEquals(5, pool.getIdleConnections(), "Idle connections not as expected");
try (Connection connection = ds.getConnection()) {
logger.log(Level.INFO, "got connection " + connection);
assertNotNull(connection);
TimeUnit.MILLISECONDS.sleep(1500);
//assertEquals(6, pool.getTotalConnections(), "Second total connections not as expected");
//assertEquals(5, pool.getIdleConnections(), "Second idle connections not as expected");
assertEquals(5, pool.getTotalConnections(), "Second total connections not as expected");
assertEquals(4, pool.getIdleConnections(), "Second idle connections not as expected");
}
//assertEquals(6, pool.getIdleConnections(), "Idle connections not as expected");
assertEquals(5, pool.getIdleConnections(), "Idle connections not as expected");
TimeUnit.SECONDS.sleep(2);
assertEquals(5, pool.getTotalConnections(), "Third total connections not as expected");
assertEquals(5, pool.getIdleConnections(), "Third idle connections not as expected");
}
}
@Test
public void testIdleTimeout2() throws Exception {
Properties properties = new Properties();
properties.put("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
PoolConfig config = new PoolConfig(properties);
config.setMaximumPoolSize(50);
config.setConnectionTestQuery("SELECT 1");
config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
System.setProperty("pool.jdbc.housekeeping.periodMs", "1000");
try (PoolDataSource ds = new PoolDataSource(config)) {
System.clearProperty("pool.jdbc.housekeeping.periodMs");
TimeUnit.SECONDS.sleep(1);
Pool pool = ds.getPool();
config.setIdleTimeout(3000);
assertEquals(50, pool.getTotalConnections(), "Total connections not as expected");
assertEquals(50, pool.getIdleConnections(), "Idle connections not as expected");
try (Connection connection = ds.getConnection()) {
assertNotNull(connection);
TimeUnit.MILLISECONDS.sleep(1500);
assertEquals(50, pool.getTotalConnections(), "Second total connections not as expected");
assertEquals(49, pool.getIdleConnections(), "Second idle connections not as expected");
}
assertEquals(50, pool.getIdleConnections(), "Idle connections not as expected");
TimeUnit.SECONDS.sleep(3);
assertEquals(50, pool.getTotalConnections(), "Third total connections not as expected");
assertEquals(50, pool.getIdleConnections(), "Third idle connections not as expected");
}
}
}

@ -0,0 +1,70 @@
package org.xbib.io.pool.jdbc;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import java.util.Locale;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import org.xbib.jdbc.connection.pool.IsolationLevel;
public class PoolTestExtension implements BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext context) {
//Level level = Level.INFO;
Level level = Level.ALL;
System.setProperty("java.util.logging.SimpleFormatter.format",
"%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n");
LogManager.getLogManager().reset();
Logger rootLogger = LogManager.getLogManager().getLogger("");
Handler handler = new ConsoleHandler();
handler.setFormatter(new SimpleFormatter());
rootLogger.addHandler(handler);
rootLogger.setLevel(level);
for (Handler h : rootLogger.getHandlers()) {
handler.setFormatter(new SimpleFormatter());
h.setLevel(level);
}
}
public static void quietlySleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* Get the int value of a transaction isolation level by name.
*
* @param transactionIsolationName the name of the transaction isolation level
* @return the int value of the isolation level or -1
*/
public static int getTransactionIsolation(final String transactionIsolationName) {
if (transactionIsolationName != null) {
try {
final String upperCaseIsolationLevelName = transactionIsolationName.toUpperCase(Locale.ENGLISH);
return IsolationLevel.valueOf(upperCaseIsolationLevelName).getLevelId();
} catch (IllegalArgumentException e) {
try {
final int level = Integer.parseInt(transactionIsolationName);
for (IsolationLevel iso : IsolationLevel.values()) {
if (iso.getLevelId() == level) {
return iso.getLevelId();
}
}
throw new IllegalArgumentException("Invalid transaction isolation value: " + transactionIsolationName);
}
catch (NumberFormatException nfe) {
throw new IllegalArgumentException("Invalid transaction isolation value: " + transactionIsolationName, nfe);
}
}
}
return -1;
}
}

@ -0,0 +1,247 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.io.pool.jdbc.mock.StubConnection;
import org.xbib.io.pool.jdbc.mock.StubStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
@ExtendWith(PoolTestExtension.class)
public class ProxiesTest {
@Test
public void testProxyCreation() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
Connection conn = ds.getConnection();
assertNotNull(conn.createStatement(ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE));
assertNotNull(conn.createStatement(ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT));
assertNotNull(conn.prepareCall("some sql"));
assertNotNull(conn.prepareCall("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE));
assertNotNull(conn.prepareCall("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT));
assertNotNull(conn.prepareStatement("some sql", PreparedStatement.NO_GENERATED_KEYS));
assertNotNull(conn.prepareStatement("some sql", new int[3]));
assertNotNull(conn.prepareStatement("some sql", new String[3]));
assertNotNull(conn.prepareStatement("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE));
assertNotNull(conn.prepareStatement("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT));
assertNotNull(conn.toString());
assertTrue(conn.isWrapperFor(Connection.class));
assertTrue(conn.isValid(10));
assertFalse(conn.isClosed());
assertNotNull(conn.unwrap(StubConnection.class));
try {
conn.unwrap(ProxiesTest.class);
fail();
} catch (SQLException e) {
// pass
}
}
}
@Test
public void testStatementProxy() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
Connection conn = ds.getConnection();
PreparedStatement stmt = conn.prepareStatement("some sql");
stmt.executeQuery();
stmt.executeQuery("some sql");
assertFalse(stmt.isClosed());
assertNotNull(stmt.getGeneratedKeys());
assertNotNull(stmt.getResultSet());
assertNotNull(stmt.getConnection());
assertNotNull(stmt.unwrap(StubStatement.class));
try {
stmt.unwrap(ProxiesTest.class);
fail();
} catch (SQLException e) {
// pass
}
}
}
@Test
public void testStatementExceptions() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(1));
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
Connection conn = ds.getConnection();
StubConnection stubConnection = conn.unwrap(StubConnection.class);
stubConnection.throwException = true;
try {
conn.createStatement();
fail();
} catch (SQLException e) {
// pass
}
try {
conn.createStatement(0, 0);
fail();
} catch (SQLException e) {
// pass
}
try {
conn.createStatement(0, 0, 0);
fail();
} catch (SQLException e) {
// pass
}
try {
conn.prepareCall("");
fail();
} catch (SQLException e) {
// pass
}
try {
conn.prepareCall("", 0, 0);
fail();
} catch (SQLException e) {
// pass
}
try {
conn.prepareCall("", 0, 0, 0);
fail();
} catch (SQLException e) {
// pass
}
try {
conn.prepareStatement("");
fail();
} catch (SQLException e) {
// pass
}
try {
conn.prepareStatement("", 0);
fail();
} catch (SQLException e) {
// pass
}
try {
conn.prepareStatement("", new int[0]);
fail();
} catch (SQLException e) {
// pass
}
try {
conn.prepareStatement("", new String[0]);
fail();
} catch (SQLException e) {
// pass
}
try {
conn.prepareStatement("", 0, 0);
fail();
} catch (SQLException e) {
// pass
}
try {
conn.prepareStatement("", 0, 0, 0);
fail();
} catch (SQLException e) {
// pass
}
}
}
@Test
public void testOtherExceptions() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection conn = ds.getConnection()) {
StubConnection stubConnection = conn.unwrap(StubConnection.class);
stubConnection.throwException = true;
try {
conn.setTransactionIsolation(Connection.TRANSACTION_NONE);
fail();
} catch (SQLException e) {
// pass
}
try {
conn.isReadOnly();
fail();
} catch (SQLException e) {
// pass
}
try {
conn.setReadOnly(false);
fail();
} catch (SQLException e) {
// pass
}
try {
conn.setCatalog("");
fail();
} catch (SQLException e) {
// pass
}
try {
conn.setAutoCommit(false);
fail();
} catch (SQLException e) {
// pass
}
try {
conn.clearWarnings();
fail();
} catch (SQLException e) {
// pass
}
try {
conn.isValid(0);
fail();
} catch (SQLException e) {
// pass
}
try {
conn.isWrapperFor(getClass());
fail();
} catch (SQLException e) {
// pass
}
try {
conn.unwrap(getClass());
fail();
} catch (SQLException e) {
// pass
}
try {
conn.close();
fail();
} catch (SQLException e) {
// pass
}
try {
assertFalse(conn.isValid(0));
} catch (SQLException e) {
fail();
}
}
}
}
}

@ -0,0 +1,40 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertSame;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import java.sql.Connection;
import org.xbib.jdbc.connection.pool.Pool;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
@ExtendWith(PoolTestExtension.class)
public class RampUpDownTest {
@Test
public void rampUpDownTest() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(5);
config.setMaximumPoolSize(60);
config.setInitializationFailTimeout(0);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
config.setIdleTimeout(1000);
config.setHousekeepingPeriodMs(250L);
try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool();
assertSame(1, pool.getTotalConnections(), "total connections not as expected");
PoolTestExtension.quietlySleep(500);
Connection[] connections = new Connection[ds.getPool().getConfig().getMaximumPoolSize()];
for (int i = 0; i < connections.length; i++) {
connections[i] = ds.getConnection();
}
assertSame(60, pool.getTotalConnections(), "total connections not as expected");
for (Connection connection : connections) {
connection.close();
}
PoolTestExtension.quietlySleep(500);
assertSame(60, pool.getIdleConnections(), "idle connections not as expected");
}
}
}

@ -0,0 +1,111 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.io.pool.jdbc.mock.StubConnection;
import org.xbib.io.pool.jdbc.mock.StubStatement;
import org.xbib.jdbc.connection.pool.util.ClockSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
@ExtendWith(PoolTestExtension.class)
public class SaturatedPoolTest830 {
private static final Logger logger = Logger.getLogger(SaturatedPoolTest830.class.getName());
private static final int MAX_POOL_SIZE = 10;
@Test
public void saturatedPoolTest() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(5);
config.setMaximumPoolSize(MAX_POOL_SIZE);
config.setInitializationFailTimeout(Long.MAX_VALUE);
config.setConnectionTimeout(1000);
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
config.setHousekeepingPeriodMs(5000L);
StubConnection.slowCreate = true;
StubStatement.setSimulatedQueryTime(1000);
final long start = ClockSource.currentTime();
try (PoolDataSource ds = new PoolDataSource(config)) {
LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 50, 50,
2, TimeUnit.SECONDS, queue, new ThreadPoolExecutor.CallerRunsPolicy());
threadPool.allowCoreThreadTimeOut(true);
AtomicInteger windowIndex = new AtomicInteger();
boolean[] failureWindow = new boolean[100];
Arrays.fill(failureWindow, true);
for (int i = 0; i < 50; i++) {
threadPool.execute(() -> {
try (Connection conn = ds.getConnection();
Statement stmt = conn.createStatement()) {
stmt.execute("SELECT bogus FROM imaginary");
}
catch (SQLException e) {
logger.info(e.getMessage());
}
});
}
long sleep = 80;
outer: while (true) {
PoolTestExtension.quietlySleep(sleep);
if (ClockSource.elapsedMillis(start) > TimeUnit.SECONDS.toMillis(12) && sleep < 100) {
sleep = 100;
logger.warning("switching to 100ms sleep");
}
else if (ClockSource.elapsedMillis(start) > TimeUnit.SECONDS.toMillis(6) && sleep < 90) {
sleep = 90;
logger.warning("switching to 90ms sleep");
}
threadPool.execute(() -> {
int ndx = windowIndex.incrementAndGet() % failureWindow.length;
try (Connection conn = ds.getConnection();
Statement stmt = conn.createStatement()) {
stmt.execute("SELECT bogus FROM imaginary");
failureWindow[ndx] = false;
}
catch (SQLException e) {
logger.info(e.getMessage());
failureWindow[ndx] = true;
}
});
for (boolean b : failureWindow) {
if (b) {
if (ClockSource.elapsedMillis(start) % (TimeUnit.SECONDS.toMillis(1) - sleep) < sleep) {
logger.info(MessageFormat.format("active threads {0}, submissions per second {1}, waiting threads {2}",
threadPool.getActiveCount(),
TimeUnit.SECONDS.toMillis(1) / sleep,
ds.getPool().getThreadsAwaitingConnection()));
}
continue outer;
}
}
logger.info(MessageFormat.format("active threads {0}, submissions per second {1}, waiting threads {2}",
threadPool.getActiveCount(),
TimeUnit.SECONDS.toMillis(1) / sleep,
ds.getPool().getThreadsAwaitingConnection()));
break;
}
logger.info("waiting for completion of active tasks: " + threadPool.getActiveCount());
while (ds.getPool().getActiveConnections() > 0) {
PoolTestExtension.quietlySleep(50);
}
assertEquals(TimeUnit.SECONDS.toMillis(1) / sleep, 10L, "Rate not in balance at 10req/s");
}
finally {
StubStatement.setSimulatedQueryTime(0);
StubConnection.slowCreate = false;
}
}
}

@ -0,0 +1,262 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.io.pool.jdbc.mock.StubConnection;
import org.xbib.jdbc.connection.pool.util.ClockSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
import org.xbib.jdbc.connection.pool.Pool;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
@ExtendWith(PoolTestExtension.class)
public class ShutdownTest {
@BeforeEach
public void beforeTest() {
StubConnection.count.set(0);
}
@AfterEach
public void afterTest() {
StubConnection.slowCreate = false;
}
@Test
public void testShutdown1() throws Exception {
assertSame(0, StubConnection.count.get(), "StubConnection count not as expected");
StubConnection.slowCreate = true;
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(10);
config.setInitializationFailTimeout(Long.MAX_VALUE);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
try {
if (ds.getConnection() != null) {
PoolTestExtension.quietlySleep(TimeUnit.SECONDS.toMillis(1));
}
}
catch (SQLException e) {
//
}
});
threads[i].setDaemon(true);
}
for (int i = 0; i < 10; i++) {
threads[i].start();
}
PoolTestExtension.quietlySleep(1800L);
assertTrue(pool.getTotalConnections() > 0, "total connection count not as expected");
ds.close();
assertSame(0, pool.getActiveConnections(), "active connection count not as expected");
assertSame( 0, pool.getIdleConnections(), "idle connection count not as expected");
assertSame(0, pool.getTotalConnections(), "total connection count not as expected");
assertTrue(ds.isClosed());
}
}
@Test
public void testShutdown2() throws Exception {
assertSame( 0, StubConnection.count.get(), "StubConnection count not as expected");
StubConnection.slowCreate = true;
PoolConfig config = new PoolConfig();
config.setMinimumIdle(10);
config.setMaximumPoolSize(10);
config.setInitializationFailTimeout(0);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool();
PoolTestExtension.quietlySleep(1200L);
assertTrue( pool.getTotalConnections() > 0, "total connection count not as expected");
ds.close();
assertSame(0, pool.getActiveConnections(), "active connection count not as expected");
assertSame(0, pool.getIdleConnections(), "idle connection count not as expected");
assertSame(0, pool.getTotalConnections(), "Total connection count not as expected");
assertTrue(ds.toString().startsWith("PoolDataSource (") && ds.toString().endsWith(")"));
}
}
@Test
public void testShutdown3() throws Exception {
assertSame(0, StubConnection.count.get(), "StubConnection count not as expected");
StubConnection.slowCreate = false;
PoolConfig config = new PoolConfig();
config.setMinimumIdle(5);
config.setMaximumPoolSize(5);
config.setInitializationFailTimeout(0);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool();
PoolTestExtension.quietlySleep(1200L);
assertEquals(5, pool.getTotalConnections(), "total connection count not as expected");
ds.close();
assertSame(0, pool.getActiveConnections(), "active connection count not as expected");
assertSame( 0, pool.getIdleConnections(), "idle connection count not as expected");
assertSame(0, pool.getTotalConnections(), "total connection count not as expected");
}
}
@Test
public void testShutdown4() throws Exception {
StubConnection.slowCreate = true;
PoolConfig config = new PoolConfig();
config.setMinimumIdle(10);
config.setMaximumPoolSize(10);
config.setInitializationFailTimeout(Long.MAX_VALUE);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
PoolTestExtension.quietlySleep(500L);
ds.close();
long startTime = ClockSource.currentTime();
while (ClockSource.elapsedMillis(startTime) < TimeUnit.SECONDS.toMillis(5) && threadCount() > 0) {
PoolTestExtension.quietlySleep(250);
}
assertSame(0, ds.getPool().getTotalConnections(), "unreleased connections after shutdown");
}
}
@Test
public void testShutdown5() throws Exception {
assertSame( 0, StubConnection.count.get(), "StubConnection count not as expected");
PoolConfig config = new PoolConfig();
config.setMinimumIdle(5);
config.setMaximumPoolSize(5);
config.setInitializationFailTimeout(0);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool();
for (int i = 0; i < 5; i++) {
ds.getConnection();
}
assertEquals(5, pool.getTotalConnections(), "total connection count not as expected, ");
ds.close();
assertSame(0, pool.getActiveConnections(), "active connection count not as expected");
assertSame(0, pool.getIdleConnections(), "idle connection count not as expected");
assertSame(0, pool.getTotalConnections(), "total connection count not as expected");
}
}
@Test
public void testAfterShutdown() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(5);
config.setInitializationFailTimeout(0);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
ds.close();
try {
ds.getConnection();
}
catch (SQLException e) {
assertTrue(e.getMessage().contains("has been closed"));
}
}
}
@Test
public void testShutdownDuringInit() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(5);
config.setMaximumPoolSize(5);
config.setConnectionTimeout(1000);
config.setValidationTimeout(1000);
config.setInitializationFailTimeout(0);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
StubConnection.slowCreate = true;
PoolTestExtension.quietlySleep(3000L);
}
}
@Test
public void testThreadedShutdown() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(5);
config.setMaximumPoolSize(5);
config.setConnectionTimeout(1000);
config.setValidationTimeout(1000);
config.setInitializationFailTimeout(0);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
for (int i = 0; i < 4; i++) {
try (PoolDataSource ds = new PoolDataSource(config)) {
Thread t = new Thread(() -> {
try (Connection connection = ds.getConnection()) {
for (int i1 = 0; i1 < 10; i1++) {
Connection connection2 = null;
try {
connection2 = ds.getConnection();
PreparedStatement stmt = connection2.prepareStatement("SOMETHING");
PoolTestExtension.quietlySleep(20);
stmt.getMaxFieldSize();
}
catch (SQLException e) {
try {
if (connection2 != null) {
connection2.close();
}
}
catch (SQLException e2) {
if (e2.getMessage().contains("shutdown") || e2.getMessage().contains("evicted")) {
break;
}
}
}
}
}
catch (Exception e) {
fail(e.getMessage());
}
finally {
ds.close();
}
});
t.start();
Thread t2 = new Thread(() -> {
PoolTestExtension.quietlySleep(100);
try {
ds.close();
}
catch (IllegalStateException e) {
fail(e.getMessage());
}
});
t2.start();
t.join();
t2.join();
}
}
}
private int threadCount() {
Thread[] threads = new Thread[Thread.activeCount() * 2];
Thread.enumerate(threads);
int count = 0;
for (Thread thread : threads) {
count += (thread != null && thread.getName().startsWith("Pool")) ? 1 : 0;
}
return count;
}
}

@ -0,0 +1,98 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import org.xbib.jdbc.connection.pool.Pool;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
public class StatementTest {
private PoolDataSource ds;
@BeforeEach
public void setup() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(2);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
ds = new PoolDataSource(config);
}
@AfterEach
public void teardown() {
ds.close();
}
@Test
public void testStatementClose() throws SQLException {
ds.getConnection().close();
Pool pool = ds.getPool();
assertTrue(pool.getTotalConnections() >= 1, "total connections not as expected");
assertTrue(pool.getIdleConnections() >= 1, "idle connections not as expected");
try (Connection connection = ds.getConnection()) {
assertNotNull(connection);
assertTrue(pool.getTotalConnections() >= 1, "total connections not as expected");
assertTrue(pool.getIdleConnections() >= 0, "idle connections not as expected");
Statement statement = connection.createStatement();
assertNotNull(statement);
connection.close();
assertTrue(statement.isClosed());
}
}
@Test
public void testAutoStatementClose() throws SQLException {
try (Connection connection = ds.getConnection()) {
assertNotNull(connection);
Statement statement1 = connection.createStatement();
assertNotNull(statement1);
Statement statement2 = connection.createStatement();
assertNotNull(statement2);
connection.close();
assertTrue(statement1.isClosed());
assertTrue(statement2.isClosed());
}
}
@Test
public void testStatementResultSetProxyClose() throws SQLException {
try (Connection connection = ds.getConnection()) {
assertNotNull(connection);
Statement statement1 = connection.createStatement();
assertNotNull(statement1);
Statement statement2 = connection.createStatement();
assertNotNull(statement2);
statement1.getResultSet().getStatement().close();
statement2.getGeneratedKeys().getStatement().close();
assertTrue(statement1.isClosed());
assertTrue(statement2.isClosed());
}
}
@Test
public void testDoubleStatementClose() throws SQLException {
try (Connection connection = ds.getConnection();
Statement statement1 = connection.createStatement()) {
statement1.close();
statement1.close();
}
}
@Test
public void testOutOfOrderStatementClose() throws SQLException {
try (Connection connection = ds.getConnection();
Statement statement1 = connection.createStatement();
Statement statement2 = connection.createStatement()) {
statement1.close();
statement2.close();
}
}
}

@ -0,0 +1,59 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.xbib.io.pool.jdbc.mock.StubConnection;
import org.xbib.io.pool.jdbc.mock.StubDataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xbib.jdbc.connection.pool.PoolConfig;
import org.xbib.jdbc.connection.pool.PoolDataSource;
public class UnwrapTest {
@Test
public void testUnwrapConnection() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setInitializationFailTimeout(0);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
ds.getConnection().close();
assertSame(1, ds.getPool().getIdleConnections(), "Idle connections not as expected");
Connection connection = ds.getConnection();
assertNotNull(connection);
StubConnection unwrapped = connection.unwrap(StubConnection.class);
assertNotNull(unwrapped, "unwrapped connection is not instance of StubConnection: " + unwrapped);
}
}
@Test
public void testUnwrapDataSource() throws Exception {
PoolConfig config = new PoolConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setInitializationFailTimeout(0);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
StubDataSource unwrap = ds.unwrap(StubDataSource.class);
assertNotNull(unwrap);
assertTrue(ds.isWrapperFor(PoolDataSource.class));
assertNotNull(ds.unwrap(PoolDataSource.class));
assertFalse(ds.isWrapperFor(getClass()));
try {
ds.unwrap(getClass());
} catch (SQLException e) {
Logger.getAnonymousLogger().log(Level.INFO, e.getMessage());
assertTrue(e.getMessage().contains("wrapped DataSource"));
}
}
}
}

@ -0,0 +1,96 @@
package org.xbib.io.pool.jdbc.mock;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.PrintWriter;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Statement;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class MockDataSource implements DataSource {
@Override
public Connection getConnection() throws SQLException {
return createMockConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return getConnection();
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public static Connection createMockConnection() throws SQLException {
final Connection mockConnection = mock(Connection.class);
when(mockConnection.getAutoCommit()).thenReturn(true);
Statement statement = mock(Statement.class);
when(mockConnection.createStatement()).thenReturn(statement);
when(mockConnection.createStatement(anyInt(), anyInt())).thenReturn(statement);
when(mockConnection.createStatement(anyInt(), anyInt(), anyInt())).thenReturn(statement);
when(mockConnection.isValid(anyInt())).thenReturn(true);
PreparedStatement mockPreparedStatement = mock(PreparedStatement.class);
when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), any(int[].class))).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), any(String[].class))).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
return null;
}
}).doNothing().when(mockPreparedStatement).setInt(anyInt(), anyInt());
ResultSet mockResultSet = mock(ResultSet.class);
when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet);
when(mockResultSet.getString(anyInt())).thenReturn("aString");
when(mockResultSet.next()).thenReturn(true);
CallableStatement mockCallableStatement = mock(CallableStatement.class);
when(mockConnection.prepareCall(anyString())).thenReturn(mockCallableStatement);
when(mockConnection.prepareCall(anyString(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
when(mockConnection.prepareCall(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
return mockConnection;
}
}

@ -0,0 +1,33 @@
package org.xbib.io.pool.jdbc.mock;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
public abstract class StubBaseConnection implements Connection {
public volatile boolean throwException;
/**
* {@inheritDoc}
*/
@Override
public Statement createStatement() throws SQLException {
if (throwException) {
throw new SQLException();
}
return new StubStatement(this);
}
/**
* {@inheritDoc}
*/
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
if (throwException) {
throw new SQLException();
}
return new StubPreparedStatement(this);
}
}

@ -0,0 +1,522 @@
package org.xbib.io.pool.jdbc.mock;
import org.xbib.io.pool.jdbc.PoolTestExtension;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
public class StubConnection extends StubBaseConnection implements Connection {
public static final AtomicInteger count = new AtomicInteger();
public static volatile boolean slowCreate;
public static volatile boolean oldDriver;
private static final long foo;
private boolean autoCommit;
private int isolation = Connection.TRANSACTION_READ_COMMITTED;
private String catalog;
static {
foo = System.currentTimeMillis();
}
public StubConnection() {
count.incrementAndGet();
if (slowCreate) {
PoolTestExtension.quietlySleep(1000);
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
if (throwException) {
throw new SQLException();
}
if (iface.isInstance(this)) {
return (T) this;
}
throw new SQLException("Wrapped connection is not an instance of " + iface);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
if (throwException) {
throw new SQLException();
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
if (throwException) {
throw new SQLException();
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public String nativeSQL(String sql) throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
if (throwException) {
throw new SQLException();
}
this.autoCommit = autoCommit;
}
/**
* {@inheritDoc}
*/
@Override
public boolean getAutoCommit() throws SQLException {
return autoCommit;
}
/**
* {@inheritDoc}
*/
@Override
public void commit() throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void rollback() throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public boolean isClosed() throws SQLException {
if (throwException) {
throw new SQLException();
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public DatabaseMetaData getMetaData() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
if (throwException) {
throw new SQLException();
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isReadOnly() throws SQLException {
if (throwException) {
throw new SQLException();
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void setCatalog(String catalog) throws SQLException {
if (throwException) {
throw new SQLException();
}
this.catalog = catalog;
}
/**
* {@inheritDoc}
*/
@Override
public String getCatalog() throws SQLException {
return catalog;
}
/**
* {@inheritDoc}
*/
@Override
public void setTransactionIsolation(int level) throws SQLException {
if (throwException) {
throw new SQLException();
}
this.isolation = level;
}
/**
* {@inheritDoc}
*/
@Override
public int getTransactionIsolation() throws SQLException {
return isolation;
}
/**
* {@inheritDoc}
*/
@Override
public SQLWarning getWarnings() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void clearWarnings() throws SQLException {
if (throwException) {
throw new SQLException();
}
}
/**
* {@inheritDoc}
*/
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
if (throwException) {
throw new SQLException();
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
if (throwException) {
throw new SQLException();
}
return new StubPreparedStatement(this);
}
/**
* {@inheritDoc}
*/
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
if (throwException) {
throw new SQLException();
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Map<String, Class<?>> getTypeMap() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setHoldability(int holdability) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public int getHoldability() throws SQLException {
return (int) foo;
}
/**
* {@inheritDoc}
*/
@Override
public Savepoint setSavepoint() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Savepoint setSavepoint(String name) throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void rollback(Savepoint savepoint) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
if (throwException) {
throw new SQLException();
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
if (throwException) {
throw new SQLException();
}
return new StubPreparedStatement(this);
}
/**
* {@inheritDoc}
*/
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
if (throwException) {
throw new SQLException();
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
if (throwException) {
throw new SQLException();
}
return new StubPreparedStatement(this);
}
/**
* {@inheritDoc}
*/
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
if (throwException) {
throw new SQLException();
}
return new StubPreparedStatement(this);
}
/**
* {@inheritDoc}
*/
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
if (throwException) {
throw new SQLException();
}
return new StubPreparedStatement(this);
}
/**
* {@inheritDoc}
*/
@Override
public Clob createClob() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Blob createBlob() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public NClob createNClob() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public SQLXML createSQLXML() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isValid(int timeout) throws SQLException {
if (throwException) {
throw new SQLException();
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
}
/**
* {@inheritDoc}
*/
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
}
/**
* {@inheritDoc}
*/
@Override
public String getClientInfo(String name) throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Properties getClientInfo() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
public void setSchema(String schema) throws SQLException {
}
/**
* {@inheritDoc}
*/
public String getSchema() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
public void abort(Executor executor) throws SQLException {
throw new SQLException("Intentional exception during abort");
}
/**
* {@inheritDoc}
*/
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
}
/**
* {@inheritDoc}
*/
public int getNetworkTimeout() throws SQLException {
if (oldDriver) {
throw new AbstractMethodError();
}
return 0;
}
}

@ -0,0 +1,127 @@
package org.xbib.io.pool.jdbc.mock;
import org.xbib.io.pool.jdbc.PoolTestExtension;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class StubDataSource implements DataSource {
private String user;
private String password;
private PrintWriter logWriter;
private SQLException throwException;
private long connectionAcquistionTime = 0;
private int loginTimeout;
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void setURL(String url) {
// we don't care
}
/**
* {@inheritDoc}
*/
@Override
public PrintWriter getLogWriter() throws SQLException {
return logWriter;
}
/**
* {@inheritDoc}
*/
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
this.logWriter = out;
}
/**
* {@inheritDoc}
*/
@Override
public void setLoginTimeout(int seconds) throws SQLException {
this.loginTimeout = seconds;
}
/**
* {@inheritDoc}
*/
@Override
public int getLoginTimeout() throws SQLException {
return loginTimeout;
}
/**
* {@inheritDoc}
*/
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return (T) this;
}
throw new SQLException("wrapped DataSource is not an instance of " + iface);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public Connection getConnection() throws SQLException {
if (throwException != null) {
throw throwException;
}
if (connectionAcquistionTime > 0) {
PoolTestExtension.quietlySleep(connectionAcquistionTime);
}
return new StubConnection();
}
/**
* {@inheritDoc}
*/
@Override
public Connection getConnection(String username, String password) throws SQLException {
return new StubConnection();
}
public void setThrowException(SQLException e) {
this.throwException = e;
}
public void setConnectionAcquistionTime(long connectionAcquisitionTime) {
this.connectionAcquistionTime = connectionAcquisitionTime;
}
}

@ -0,0 +1,78 @@
package org.xbib.io.pool.jdbc.mock;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.logging.Logger;
public class StubDriver implements Driver {
private static final Driver driver;
static {
driver = new StubDriver();
try {
DriverManager.registerDriver(driver);
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* {@inheritDoc}
*/
@Override
public Connection connect(String url, Properties info) throws SQLException {
return new StubConnection();
}
/**
* {@inheritDoc}
*/
@Override
public boolean acceptsURL(String url) throws SQLException {
return "jdbc:stub".equals(url);
}
/**
* {@inheritDoc}
*/
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public int getMajorVersion() {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int getMinorVersion() {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public boolean jdbcCompliant() {
return true;
}
/**
* {@inheritDoc}
*/
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}

@ -0,0 +1,746 @@
package org.xbib.io.pool.jdbc.mock;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.NClob;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
@SuppressWarnings("RedundantThrows")
public class StubPreparedStatement extends StubStatement implements PreparedStatement {
StubPreparedStatement(Connection connection) {
super(connection);
}
/**
* {@inheritDoc}
*/
@Override
public ResultSet executeQuery(String sql) throws SQLException {
return new StubResultSet();
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate(String sql) throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int getMaxFieldSize() throws SQLException {
throw new SQLException("Simulated disconnection error", "08999");
}
/**
* {@inheritDoc}
*/
@Override
public void setMaxFieldSize(int max) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public int getMaxRows() throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void setMaxRows(int max) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setEscapeProcessing(boolean enable) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public int getQueryTimeout() throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void setQueryTimeout(int seconds) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void cancel() throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public SQLWarning getWarnings() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void clearWarnings() throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setCursorName(String name) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute(String sql) throws SQLException {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public ResultSet getResultSet() throws SQLException {
return new StubResultSet();
}
/**
* {@inheritDoc}
*/
@Override
public int getUpdateCount() throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public boolean getMoreResults() throws SQLException {
if (isClosed()) {
throw new SQLException("Connection is closed");
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void setFetchDirection(int direction) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public int getFetchDirection() throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void setFetchSize(int rows) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public int getFetchSize() throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int getResultSetConcurrency() throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int getResultSetType() throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void addBatch(String sql) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void clearBatch() throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public int[] executeBatch() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public boolean getMoreResults(int current) throws SQLException {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public ResultSet getGeneratedKeys() throws SQLException {
return new StubResultSet();
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute(String sql, String[] columnNames) throws SQLException {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public int getResultSetHoldability() throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void setPoolable(boolean poolable) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public boolean isPoolable() throws SQLException {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void closeOnCompletion() throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public boolean isCloseOnCompletion() throws SQLException {
return false;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return (T) this;
}
throw new SQLException("Wrapped connection is not an instance of " + iface);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public ResultSet executeQuery() throws SQLException {
return new StubResultSet();
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate() throws SQLException {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void setNull(int parameterIndex, int sqlType) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setBoolean(int parameterIndex, boolean x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setByte(int parameterIndex, byte x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setShort(int parameterIndex, short x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setInt(int parameterIndex, int x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setLong(int parameterIndex, long x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setFloat(int parameterIndex, float x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setDouble(int parameterIndex, double x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setString(int parameterIndex, String x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setBytes(int parameterIndex, byte[] x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setDate(int parameterIndex, Date x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setTime(int parameterIndex, Time x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("deprecation")
public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void clearParameters() throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setObject(int parameterIndex, Object x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute() throws SQLException {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void addBatch() throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setRef(int parameterIndex, Ref x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setBlob(int parameterIndex, Blob x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setClob(int parameterIndex, Clob x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setArray(int parameterIndex, Array x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public ResultSetMetaData getMetaData() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setURL(int parameterIndex, URL x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public ParameterMetaData getParameterMetaData() throws SQLException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void setRowId(int parameterIndex, RowId x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setNString(int parameterIndex, String value) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setNClob(int parameterIndex, NClob value) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setClob(int parameterIndex, Reader reader) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
}
/**
* {@inheritDoc}
*/
@Override
public void setNClob(int parameterIndex, Reader reader) throws SQLException {
}
}

@ -0,0 +1,418 @@
package org.xbib.io.pool.jdbc.mock;
import org.xbib.io.pool.jdbc.PoolTestExtension;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLWarning;
import java.sql.Statement;
public class StubStatement implements Statement {
public static volatile boolean oldDriver;
private static volatile long simulatedQueryTime;
private boolean closed;
private final Connection connection;
public StubStatement(Connection connection) {
this.connection = connection;
}
public static void setSimulatedQueryTime(long time) {
simulatedQueryTime = time;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
checkClosed();
return (T) this;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
checkClosed();
return false;
}
/**
* {@inheritDoc}
*/
@Override
public ResultSet executeQuery(String sql) throws SQLException {
checkClosed();
return new StubResultSet();
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate(String sql) throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws SQLException {
closed = true;
}
/**
* {@inheritDoc}
*/
@Override
public int getMaxFieldSize() throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void setMaxFieldSize(int max) throws SQLException {
checkClosed();
}
/**
* {@inheritDoc}
*/
@Override
public int getMaxRows() throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void setMaxRows(int max) throws SQLException {
checkClosed();
}
/**
* {@inheritDoc}
*/
@Override
public void setEscapeProcessing(boolean enable) throws SQLException {
checkClosed();
}
/**
* {@inheritDoc}
*/
@Override
public int getQueryTimeout() throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void setQueryTimeout(int seconds) throws SQLException {
if (oldDriver) {
throw new SQLFeatureNotSupportedException();
}
checkClosed();
}
/**
* {@inheritDoc}
*/
@Override
public void cancel() throws SQLException {
checkClosed();
}
/**
* {@inheritDoc}
*/
@Override
public SQLWarning getWarnings() throws SQLException {
checkClosed();
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void clearWarnings() throws SQLException {
checkClosed();
}
/**
* {@inheritDoc}
*/
@Override
public void setCursorName(String name) throws SQLException {
checkClosed();
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute(String sql) throws SQLException {
checkClosed();
if (simulatedQueryTime > 0) {
PoolTestExtension.quietlySleep(simulatedQueryTime);
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public ResultSet getResultSet() throws SQLException {
checkClosed();
return new StubResultSet();
}
/**
* {@inheritDoc}
*/
@Override
public int getUpdateCount() throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public boolean getMoreResults() throws SQLException {
checkClosed();
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void setFetchDirection(int direction) throws SQLException {
checkClosed();
}
/**
* {@inheritDoc}
*/
@Override
public int getFetchDirection() throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void setFetchSize(int rows) throws SQLException {
checkClosed();
}
/**
* {@inheritDoc}
*/
@Override
public int getFetchSize() throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int getResultSetConcurrency() throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int getResultSetType() throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void addBatch(String sql) throws SQLException {
checkClosed();
}
/**
* {@inheritDoc}
*/
@Override
public void clearBatch() throws SQLException {
checkClosed();
}
/**
* {@inheritDoc}
*/
@Override
public int[] executeBatch() throws SQLException {
checkClosed();
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Connection getConnection() throws SQLException {
checkClosed();
return connection;
}
/**
* {@inheritDoc}
*/
@Override
public boolean getMoreResults(int current) throws SQLException {
checkClosed();
return false;
}
/**
* {@inheritDoc}
*/
@Override
public ResultSet getGeneratedKeys() throws SQLException {
checkClosed();
return new StubResultSet();
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
checkClosed();
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
checkClosed();
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute(String sql, String[] columnNames) throws SQLException {
checkClosed();
return false;
}
/**
* {@inheritDoc}
*/
@Override
public int getResultSetHoldability() throws SQLException {
checkClosed();
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isClosed() throws SQLException {
return closed;
}
/**
* {@inheritDoc}
*/
@Override
public void setPoolable(boolean poolable) throws SQLException {
checkClosed();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isPoolable() throws SQLException {
checkClosed();
return false;
}
/**
* {@inheritDoc}
*/
public void closeOnCompletion() throws SQLException {
checkClosed();
}
/**
* {@inheritDoc}
*/
public boolean isCloseOnCompletion() throws SQLException {
checkClosed();
return false;
}
private void checkClosed() throws SQLException {
if (closed) {
throw new SQLException("Statement is closed");
}
}
}

@ -0,0 +1,7 @@
dependencies {
api project(":jdbc-connection-pool")
testImplementation "org.apache.derby:derby:${project.property('derby.version')}"
testImplementation "org.testcontainers:testcontainers:${project.property('testcontainers.version')}"
testImplementation "org.testcontainers:junit-jupiter:${project.property('testcontainers.version')}"
testImplementation "org.testcontainers:oracle-xe:${project.property('testcontainers.version')}"
}

@ -0,0 +1,4 @@
module org.xbib.jdbc.query {
requires transitive org.xbib.jdbc.connection.pool;
exports org.xbib.jdbc.query;
}

@ -0,0 +1,127 @@
package org.xbib.jdbc.query;
import java.math.BigDecimal;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Entry point for getting configuration parameters. This isn't intended as
* a be-all, end-all configuration solution. Just a way of easily specifying
* multiple read-only sources for configuration with a nice fluent syntax.
*/
public interface Config extends Function<String, String>, Supplier<Config> {
/**
* Convenience method for fluent syntax.
*
* @return a builder for specifying from where configuration should be loaded
*/
static ConfigFrom from() {
return new ConfigFromImpl();
}
// TODO add: String originalKey(String key) to find out the key before prefixing or other manipulation
/**
* @return a trimmed, non-empty string, or null
*/
String getString(String key);
/**
* @return a trimmed, non-empty string
* @throws ConfigMissingException if no value could be read for the specified key
*/
String getStringOrThrow(String key);
String getString(String key, String defaultValue);
/**
* Same as {@link #getString(String)}. Useful for passing configs around
* without static dependencies.
*/
@Override
default String apply(String key) {
return getString(key);
}
@Override
default Config get() {
return this;
}
Integer getInteger(String key);
int getInteger(String key, int defaultValue);
/**
* @throws ConfigMissingException if no value could be read for the specified key
*/
int getIntegerOrThrow(String key);
Long getLong(String key);
long getLong(String key, long defaultValue);
/**
* @throws ConfigMissingException if no value could be read for the specified key
*/
long getLongOrThrow(String key);
Float getFloat(String key);
float getFloat(String key, float defaultValue);
/**
* @throws ConfigMissingException if no value could be read for the specified key
*/
float getFloatOrThrow(String key);
Double getDouble(String key);
double getDouble(String key, double defaultValue);
/**
* @throws ConfigMissingException if no value could be read for the specified key
*/
double getDoubleOrThrow(String key);
BigDecimal getBigDecimal(String key);
BigDecimal getBigDecimal(String key, BigDecimal defaultValue);
/**
* @throws ConfigMissingException if no value could be read for the specified key
*/
BigDecimal getBigDecimalOrThrow(String key);
/**
* Read a boolean value from the configuration. The value is not case-sensitivie,
* and may be either true/false or yes/no. If no value was provided or an invalid
* value is provided, false will be returned.
*/
boolean getBooleanOrFalse(String key);
/**
* Read a boolean value from the configuration. The value is not case-sensitivie,
* and may be either true/false or yes/no. If no value was provided or an invalid
* value is provided, true will be returned.
*/
boolean getBooleanOrTrue(String key);
/**
* @throws ConfigMissingException if no value could be read for the specified key
*/
boolean getBooleanOrThrow(String key);
/**
* Show where configuration is coming from. This is useful to drop in your logs
* for troubleshooting.
*/
String sources();
}

@ -0,0 +1,102 @@
package org.xbib.jdbc.query;
import java.io.File;
import java.nio.charset.CharsetDecoder;
import java.util.Properties;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Pull configuration properties from various sources and filter/manipulate them.
*/
public interface ConfigFrom extends Supplier<Config> {
/**
* Convenience method for fluent syntax.
*
* @return a builder for specifying from where configuration should be loaded
*/
static ConfigFrom firstOf() {
return new ConfigFromImpl();
}
static Config other(Function<String, String> other) {
if (other instanceof Config) {
return (Config) other;
}
return new ConfigFromImpl().custom(other::apply).get();
}
ConfigFrom custom(Function<String, String> keyValueLookup);
ConfigFrom value(String key, String value);
ConfigFrom systemProperties();
ConfigFrom env();
ConfigFrom properties(Properties properties);
ConfigFrom config(Config config);
ConfigFrom config(Supplier<Config> config);
/**
* Adds a set of properties files to read from, which can be overridden by a system property "properties".
* Equivalent to:
* <pre>
* defaultPropertyFiles("properties", "conf/app.properties", "local.properties", "sample.properties")
* </pre>
*/
ConfigFrom defaultPropertyFiles();
/**
* Adds a set of properties files to read from, which can be overridden by a specified system property.
* Equivalent to:
* <pre>
* defaultPropertyFiles(systemPropertyKey, Charset.defaultCharset().newDecoder(), filenames)
* </pre>
*/
ConfigFrom defaultPropertyFiles(String systemPropertyKey, String... filenames);
/**
* Adds a set of properties files to read from, which can be overridden by a specified system property.
* Equivalent to:
* <pre>
* propertyFile(Charset.defaultCharset().newDecoder(),
* System.getProperty(systemPropertyKey, String.join(File.pathSeparator, filenames))
* .split(File.pathSeparator));
* </pre>
*/
ConfigFrom defaultPropertyFiles(String systemPropertyKey, CharsetDecoder decoder, String... filenames);
ConfigFrom propertyFile(String... filenames);
ConfigFrom propertyFile(CharsetDecoder decoder, String... filenames);
ConfigFrom propertyFile(File... files);
ConfigFrom propertyFile(CharsetDecoder decoder, File... files);
ConfigFrom rename(String key, String newKey);
ConfigFrom includeKeys(String... keys);
ConfigFrom includePrefix(String... prefixes);
ConfigFrom includeRegex(String regex);
ConfigFrom excludeKeys(String... keys);
ConfigFrom excludePrefix(String... prefixes);
ConfigFrom excludeRegex(String regex);
ConfigFrom removePrefix(String... prefixes);
ConfigFrom addPrefix(String prefix);
ConfigFrom substitutions(Config config);
Config get();
}

@ -0,0 +1,295 @@
package org.xbib.jdbc.query;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Access configuration properties from a variety of standard sources,
* and provide some basic filtering and mapping of property keys.
*/
public class ConfigFromImpl implements ConfigFrom {
private final List<Config> searchPath = new ArrayList<>();
public ConfigFromImpl() {
super();
}
public ConfigFromImpl(Config first) {
searchPath.add(first);
}
@Override
public ConfigFrom custom(Function<String, String> keyValueLookup) {
return custom(keyValueLookup, "custom()");
}
private ConfigFrom custom(Function<String, String> keyValueLookup, String source) {
searchPath.add(new ConfigImpl(keyValueLookup, source));
return this;
}
@Override
public ConfigFrom value(String key, String value) {
return custom(k -> k.equals(key) ? value : null, "value(" + key + ")");
}
@Override
public ConfigFrom config(Config config) {
searchPath.add(config);
return this;
}
@Override
public ConfigFrom config(Supplier<Config> config) {
return config(config.get());
}
@Override
public ConfigFrom systemProperties() {
return custom(System::getProperty, "systemProperties()");
}
@Override
public ConfigFrom env() {
return custom(System::getenv, "env()");
}
@Override
public ConfigFrom properties(Properties properties) {
return custom(properties::getProperty, "properties()");
}
@Override
public ConfigFrom defaultPropertyFiles() {
return defaultPropertyFiles("properties", "conf/app.properties", "local.properties", "sample.properties");
}
@Override
public ConfigFrom defaultPropertyFiles(String systemPropertyKey, String... filenames) {
return defaultPropertyFiles(systemPropertyKey, Charset.defaultCharset().newDecoder(), filenames);
}
@Override
public ConfigFrom defaultPropertyFiles(String systemPropertyKey, CharsetDecoder decoder, String... filenames) {
String properties = System.getProperty(systemPropertyKey, String.join(File.pathSeparator, filenames));
return propertyFile(Charset.defaultCharset().newDecoder(), properties.split(File.pathSeparator));
}
@Override
public ConfigFrom propertyFile(String... filenames) {
return propertyFile(Charset.defaultCharset().newDecoder(), filenames);
}
@Override
public ConfigFrom propertyFile(CharsetDecoder decoder, String... filenames) {
for (String filename : filenames) {
if (filename != null) {
propertyFile(decoder, new File(filename));
}
}
return this;
}
@Override
public ConfigFrom propertyFile(File... files) {
return propertyFile(Charset.defaultCharset().newDecoder(), files);
}
@Override
public ConfigFrom propertyFile(CharsetDecoder decoder, File... files) {
for (File file : files) {
if (file != null) {
try {
Properties properties = new Properties();
try (
FileInputStream fis = new FileInputStream(file);
InputStreamReader reader = new InputStreamReader(fis, decoder)
) {
properties.load(reader);
}
searchPath.add(new ConfigImpl(properties::getProperty, "propertyFile(" + file.getAbsolutePath() + ")"));
} catch (Exception e) {
// Put a "fake" provider in so we can see it failed
String fileName = file.getName();
try {
fileName = file.getAbsolutePath();
} catch (Exception ignored) {
// Fall back to relative name
}
custom(k -> null, "Ignored: propertyFile(" + fileName + ") " + e.getClass().getSimpleName());
}
}
}
return this;
}
@Override
public ConfigFrom rename(String configKey, String newKey) {
return new ConfigFromImpl(new ConfigImpl(key -> {
if (key.equals(configKey)) {
return null;
}
if (key.equals(newKey)) {
return lookup(configKey);
}
return lookup(key);
}, indentedSources("rename(" + configKey + " -> " + newKey + ")")));
}
@Override
public ConfigFrom includeKeys(String... keys) {
return new ConfigFromImpl(new ConfigImpl(key -> {
for (String k : keys) {
if (key.equals(k)) {
return lookup(key);
}
}
return null;
}, indentedSources("includeKeys" + Arrays.asList(keys))));
}
@Override
public ConfigFrom includePrefix(String... prefixes) {
return new ConfigFromImpl(new ConfigImpl(key -> {
for (String prefix : prefixes) {
if (key.startsWith(prefix)) {
return lookup(key);
}
}
return null;
}, indentedSources("includePrefix" + Arrays.asList(prefixes))));
}
@Override
public ConfigFrom includeRegex(String regex) {
return new ConfigFromImpl(new ConfigImpl(key -> {
if (key.matches(regex)) {
return lookup(key);
}
return null;
}, indentedSources("includeRegex(" + regex + ")")));
}
@Override
public ConfigFrom excludeKeys(String... keys) {
return new ConfigFromImpl(new ConfigImpl(key -> {
for (String k : keys) {
if (key.equals(k)) {
return null;
}
}
return lookup(key);
}, indentedSources("excludeKeys" + Arrays.asList(keys))));
}
@Override
public ConfigFrom excludePrefix(String... prefixes) {
return new ConfigFromImpl(new ConfigImpl(key -> {
for (String prefix : prefixes) {
if (key.startsWith(prefix)) {
return null;
}
}
return lookup(key);
}, indentedSources("excludePrefix" + Arrays.asList(prefixes))));
}
@Override
public ConfigFrom excludeRegex(String regex) {
return new ConfigFromImpl(new ConfigImpl(key -> {
if (key.matches(regex)) {
return null;
}
return lookup(key);
}, indentedSources("excludeRegex(" + regex + ")")));
}
@Override
public ConfigFrom removePrefix(String... prefixes) {
return new ConfigFromImpl(new ConfigImpl(key -> {
// Give precedence to ones that already lacked the prefix,
// do an include*() first if you don't want that
String value = lookup(key);
if (value != null) {
return value;
}
for (String prefix : prefixes) {
value = lookup(prefix + key);
if (value != null) {
return value;
}
}
return null;
}, indentedSources("removePrefix" + Arrays.asList(prefixes))));
}
@Override
public ConfigFrom addPrefix(String prefix) {
return new ConfigFromImpl(new ConfigImpl(key -> {
if (key.startsWith(prefix)) {
return lookup(key.substring(prefix.length()));
} else {
return null;
}
}, indentedSources("addPrefix(" + prefix + ")")));
}
@Override
public ConfigFrom substitutions(Config config) {
return new ConfigFromImpl(new ConfigImpl(key -> {
String value = lookup(key);
if (value != null) {
// matches ${ENV_VAR_NAME} or $ENV_VAR_NAME
Pattern p = Pattern.compile("(?<!\\$)\\$(?!\\$)\\{(\\w+)}|(?<!\\$)\\$(?!\\$)(\\w+)");
Matcher m = p.matcher(value);
StringBuffer sb = new StringBuffer();
while (m.find()) {
String envVarName = null == m.group(1) ? m.group(2) : m.group(1);
String envVarValue = config.getString(envVarName);
m.appendReplacement(sb, null == envVarValue ? "" : envVarValue);
}
m.appendTail(sb);
// Allow escaping literal $ with $$
return sb.toString().replaceAll("(\\${2})", "\\$");
} else {
return null;
}
}, indentedSources("substitutions(" + config.sources() + ")")));
}
@Override
public Config get() {
return new ConfigImpl(this::lookup, indentedSources("Config"));
}
private String indentedSources(String label) {
StringBuilder buf = new StringBuilder(label);
for (Config config : searchPath) {
buf.append(config.sources().replaceAll("(?s)^|\\n", "\n "));
}
return buf.toString();
}
private String lookup(String key) {
for (Config config : searchPath) {
String value = config.getString(key);
if (value != null) {
return value;
}
}
return null;
}
}

@ -0,0 +1,252 @@
package org.xbib.jdbc.query;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class handles all of the type conversions, default values, etc.
* to get from simple string key/value pairs to richer configuration.
*/
public class ConfigImpl implements Config {
private static final Logger log = Logger.getLogger(ConfigFromImpl.class.getName());
private final Function<String, String> provider;
private final String sources;
private final Set<String> failedKeys = new HashSet<>();
public ConfigImpl(Function<String, String> provider, String sources) {
this.provider = provider;
this.sources = sources;
}
@Override
public String getString(String key) {
return cleanString(key);
}
@Override
public String getStringOrThrow(String key) {
return nonnull(key, getString(key));
}
@Override
public String getString(String key, String defaultValue) {
String stringValue = cleanString(key);
if (stringValue != null) {
return stringValue;
}
// Make sure the default value is tidied the same way a value would be
defaultValue = defaultValue.trim();
if (defaultValue.length() == 0) {
throw new IllegalArgumentException("Your default value is empty or just whitespace");
}
return defaultValue;
}
@Override
public Integer getInteger(String key) {
String stringValue = cleanString(key);
try {
return stringValue == null ? null : Integer.parseInt(stringValue);
} catch (Exception e) {
if (!failedKeys.contains(key)) {
log.log(Level.SEVERE, "Could not load config value for key (this message will only be logged once): " + key, e);
failedKeys.add(key);
}
return null;
}
}
@Override
public int getInteger(String key, int defaultValue) {
Integer value = getInteger(key);
return value == null ? defaultValue : value;
}
@Override
public int getIntegerOrThrow(String key) {
return nonnull(key, getInteger(key));
}
@Override
public Long getLong(String key) {
String stringValue = cleanString(key);
try {
return stringValue == null ? null : Long.parseLong(stringValue);
} catch (Exception e) {
if (!failedKeys.contains(key)) {
log.log(Level.SEVERE, "Could not load config value for key (this message will only be logged once): " + key, e);
failedKeys.add(key);
}
return null;
}
}
@Override
public long getLong(String key, long defaultValue) {
Long value = getLong(key);
return value == null ? defaultValue : value;
}
@Override
public long getLongOrThrow(String key) {
return nonnull(key, getLong(key));
}
@Override
public Float getFloat(String key) {
String stringValue = cleanString(key);
try {
return stringValue == null ? null : Float.parseFloat(stringValue);
} catch (Exception e) {
if (!failedKeys.contains(key)) {
log.log(Level.SEVERE, "Could not load config value for key (this message will only be logged once): " + key, e);
failedKeys.add(key);
}
return null;
}
}
@Override
public float getFloat(String key, float defaultValue) {
Float value = getFloat(key);
return value == null ? defaultValue : value;
}
@Override
public float getFloatOrThrow(String key) {
return nonnull(key, getFloat(key));
}
@Override
public Double getDouble(String key) {
String stringValue = cleanString(key);
try {
return stringValue == null ? null : Double.parseDouble(stringValue);
} catch (Exception e) {
if (!failedKeys.contains(key)) {
log.log(Level.SEVERE, "Could not load config value for key (this message will only be logged once): " + key, e);
failedKeys.add(key);
}
return null;
}
}
@Override
public double getDouble(String key, double defaultValue) {
Double value = getDouble(key);
return value == null ? defaultValue : value;
}
@Override
public double getDoubleOrThrow(String key) {
return nonnull(key, getDouble(key));
}
@Override
public BigDecimal getBigDecimal(String key) {
String stringValue = cleanString(key);
try {
return stringValue == null ? null : new BigDecimal(stringValue);
} catch (Exception e) {
if (!failedKeys.contains(key)) {
log.log(Level.SEVERE, "Could not load config value for key (this message will only be logged once): " + key, e);
failedKeys.add(key);
}
return null;
}
}
@Override
public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) {
BigDecimal value = getBigDecimal(key);
return value == null ? defaultValue : value;
}
@Override
public BigDecimal getBigDecimalOrThrow(String key) {
return nonnull(key, getBigDecimal(key));
}
@Override
public boolean getBooleanOrFalse(String key) {
return parseBoolean(cleanString(key), false);
}
@Override
public boolean getBooleanOrTrue(String key) {
return parseBoolean(cleanString(key), true);
}
@Override
public boolean getBooleanOrThrow(String key) {
String value = nonnull(key, cleanString(key));
value = value.toLowerCase();
if (value.equals("yes") || value.equals("true")) {
return true;
}
if (value.equals("no") || value.equals("false")) {
return false;
}
throw new ConfigMissingException("Unrecognized boolean value for config key: " + key);
}
@Override
public String sources() {
return sources;
}
private <T> T nonnull(String key, T value) {
if (value == null) {
throw new ConfigMissingException("No value for config key: " + key);
}
return value;
}
private boolean parseBoolean(String value, boolean defaultValue) {
if (value != null) {
value = value.toLowerCase();
if (value.equals("yes") || value.equals("true")) {
return true;
}
if (value.equals("no") || value.equals("false")) {
return false;
}
}
return defaultValue;
}
private String cleanString(String key) {
String value = null;
try {
value = provider.apply(key);
if (value != null) {
value = value.trim();
if (value.length() == 0) {
value = null;
}
}
} catch (Exception e) {
if (!failedKeys.contains(key)) {
log.log(Level.SEVERE, "Could not load config value for key (this message will only be logged once): " + key, e);
failedKeys.add(key);
}
}
return value;
}
}

@ -0,0 +1,22 @@
package org.xbib.jdbc.query;
/**
* Indicates that a configuration value is present but not in a usable format.
* For example, if the value must be an integer and the configuration value
* is a non-numeric string.
*/
@SuppressWarnings("serial")
public class ConfigInvalidException extends DatabaseException {
public ConfigInvalidException(String message) {
super(message);
}
public ConfigInvalidException(Throwable cause) {
super(cause);
}
public ConfigInvalidException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,20 @@
package org.xbib.jdbc.query;
/**
* Indicates that a configuration value is required but was not present.
*/
@SuppressWarnings("serial")
public class ConfigMissingException extends DatabaseException {
public ConfigMissingException(String message) {
super(message);
}
public ConfigMissingException(Throwable cause) {
super(cause);
}
public ConfigMissingException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,23 @@
package org.xbib.jdbc.query;
/**
* This exception will be thrown when a condition arises that violates
* a stated invariant regarding the database. This might be a database
* schema "constraint violated" as thrown by the database, or could be
* caused by a violation of constraints enforced only within the code.
*/
@SuppressWarnings("serial")
public class ConstraintViolationException extends DatabaseException {
public ConstraintViolationException(String message) {
super(message);
}
public ConstraintViolationException(Throwable cause) {
super(cause);
}
public ConstraintViolationException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,211 @@
package org.xbib.jdbc.query;
import java.sql.Connection;
import java.util.Date;
import java.util.function.Supplier;
/**
* Primary class for accessing a relational (SQL) database.
*/
public interface Database extends Supplier<Database> {
/**
* Create a SQL "insert" statement for further manipulation and execution.
* Note this call does not actually execute the SQL.
*
* @param sql the SQL to execute, optionally containing indexed ("?") or
* named (":foo") parameters. To include the characters '?' or ':'
* in the SQL you must escape them with two ("??" or "::"). You
* MUST be careful not to pass untrusted strings in as SQL, since
* this will be executed in the database.
* @return an interface for further manipulating the statement; never null
*/
SqlInsert toInsert(String sql);
SqlInsert toInsert( Sql sql);
/**
* Create a SQL "select" statement for further manipulation and execution.
* Note this call does not actually execute the SQL.
*
* @param sql the SQL to execute, optionally containing indexed ("?") or
* named (":foo") parameters. To include the characters '?' or ':'
* in the SQL you must escape them with two ("??" or "::"). You
* MUST be careful not to pass untrusted strings in as SQL, since
* this will be executed in the database.
* @return an interface for further manipulating the statement; never null
*/
SqlSelect toSelect(String sql);
SqlSelect toSelect( Sql sql);
/**
* Create a SQL "update" statement for further manipulation and execution.
* Note this call does not actually execute the SQL.
*
* @param sql the SQL to execute, optionally containing indexed ("?") or
* named (":foo") parameters. To include the characters '?' or ':'
* in the SQL you must escape them with two ("??" or "::"). You
* MUST be careful not to pass untrusted strings in as SQL, since
* this will be executed in the database.
* @return an interface for further manipulating the statement; never null
*/
SqlUpdate toUpdate(String sql);
SqlUpdate toUpdate( Sql sql);
/**
* Create a SQL "delete" statement for further manipulation and execution.
* Note this call does not actually execute the SQL.
*
* @param sql the SQL to execute, optionally containing indexed ("?") or
* named (":foo") parameters. To include the characters '?' or ':'
* in the SQL you must escape them with two ("??" or "::"). You
* MUST be careful not to pass untrusted strings in as SQL, since
* this will be executed in the database.
* @return an interface for further manipulating the statement; never null
*/
SqlUpdate toDelete(String sql);
SqlUpdate toDelete( Sql sql);
/**
* Create a DDL (schema modifying) statement for further manipulation and execution.
* Note this call does not actually execute the SQL.
*
* @param sql the SQL to execute, optionally containing indexed ("?") or
* named (":foo") parameters. To include the characters '?' or ':'
* in the SQL you must escape them with two ("??" or "::"). You
* MUST be careful not to pass untrusted strings in as SQL, since
* this will be executed in the database.
* @return an interface for further manipulating the statement; never null
*/
Ddl ddl(String sql);
/**
* Read the next value from a sequence. This method helps smooth over the
* syntax differences across databases.
*/
Long nextSequenceValue( String sequenceName);
/**
* Get the value that would be used if you specify an argNowPerApp() parameter.
*/
Date nowPerApp();
/**
* Cause the underlying connection to commit its transaction immediately. This
* must be explicitly enabled (see {@link Options},
* or it will throw a {@link DatabaseException}.
*/
void commitNow();
/**
* Cause the underlying connection to roll back its transaction immediately. This
* must be explicitly enabled (see {@link Options},
* or it will throw a {@link DatabaseException}.
*/
void rollbackNow();
/**
* <p>Obtain direct access to the connection being used by this instance. Be very
* careful as this is highly likely to be unsafe and cause you great pain and
* suffering. This method is included to help ease into the library in large
* codebases where some parts still rely on direct JDBC access.</p>
*
* <p>By default this method will throw a {@link DatabaseException}. If you want
* to use this method you must explicitly enable it via
* {@link Options#allowConnectionAccess()}</p>
*/
Connection underlyingConnection();
Options options();
/**
* Access information about what kind of database we are dealing with.
*/
Flavor flavor();
/**
* <p>A little syntax sugar to make it easier to customize your SQL based on the
* specific database. For example:</p>
*
* <pre>"select 1" + db.when().oracle(" from dual")</pre>
* <pre>"select " + db.when().postgres("date_trunc('day',").other("trunc(") + ") ..."</pre>
*
* @return an interface for chaining or terminating the conditionals
*/
When when();
/**
* Convenience method to deal with mutually incompatible syntax for this. For example:
*
* <p>Oracle: 'drop sequence x'</p>
* <p>Derby: 'drop sequence x restrict'</p>"
*/
void dropSequenceQuietly(String sequenceName);
/**
* Convenience method to deal with dropping tables that may or may not exist. Some
* databases make it hard to check and conditionally drop things, so we will just
* try to drop it and ignore the errors.
*
* @param tableName the table to be dropped
*/
void dropTableQuietly(String tableName);
/**
* Convenience method to check if a table or view exists so that caller can decide
* whether to create or update a table. The table name's case is normalized using
* the database's convention unless tableName is enclosed in double quotes.
* The default catalog and schema from the DB connection will be used.
*
* @param tableName the table to be checked
* @return true if the table or view exists
*/
boolean tableExists( String tableName);
/**
* Convenience method to check whether a table or view exists or not.
* The table name's case is normalized using the database's convention
* unless tableName is enclosed in double quotes. The default catalog
* from the DB connection will be used.
*
* @param tableName the table to be checked
* @param schemaName the schema expected to contain the table
* @return true if the table or view exists
*/
boolean tableExists( String tableName, String schemaName);
/**
* Return the DB table name in the normalized form in which it is stored.
* Databases like Oracle, Derby, HSQL store their tables in upper case.
* Databases like postgres and sqlserver use lower case unless configured otherwise.
* If the caller passes in a quoted string, we will leave the name as is, removing
* the quotes.
*
* @param tableName this should be a name, not a pattern
* @return table name in appropriate format for DB lookup - original case, uppercase, or lowercase
*/
String normalizeTableName(String tableName);
/**
* Check the JVM time (and timezone) against the database and log a warning
* or throw an error if they are too far apart. It is a good idea to do this
* before you store and dates, and maybe make it part of your health checks.
* If the clocks differ by more than an hour, a DatabaseException is thrown
* suggesting you check the timezones (under the assumptions the JVM and
* database are running in different timezones).
*
* @param millisToWarn if the clocks disagree by more than this and less than
* millisToError, a warning will be dropped in the log
* @param millisToError if the clocks disagree by more than this a
* DatabaseEception will be thrown
*/
void assertTimeSynchronized(long millisToWarn, long millisToError);
/**
* Convenience method, same as {@link #assertTimeSynchronized(long, long)}
* with millisToWarn=10000 and millisToError=30000.
*/
void assertTimeSynchronized();
}

@ -0,0 +1,37 @@
package org.xbib.jdbc.query;
/**
* Indicates something went wrong accessing the database. Most often this is
* used to wrap SQLException to avoid declaring checked exceptions.
*/
@SuppressWarnings("serial")
public class DatabaseException extends RuntimeException {
public DatabaseException(String message) {
super(message);
}
public DatabaseException(Throwable cause) {
super(cause);
}
public DatabaseException(String message, Throwable cause) {
super(message, cause);
}
/**
* Wrap an exception with a DatabaseException, taking into account all known
* subtypes such that we wrap subtypes in a matching type (so we don't obscure
* the type available to catch clauses).
*
* @param message the new wrapping exception will have this message
* @param cause the exception to be wrapped
* @return the exception you should throw
*/
public static DatabaseException wrap(String message, Throwable cause) {
if (cause instanceof ConstraintViolationException) {
return new ConstraintViolationException(message, cause);
}
return new DatabaseException(message, cause);
}
}

@ -0,0 +1,280 @@
package org.xbib.jdbc.query;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.logging.Logger;
/**
* Primary class for accessing a relational (SQL) database.
*/
public class DatabaseImpl implements Database {
private static final Logger log = Logger.getLogger(Database.class.getName());
private final Connection connection;
private final Options options;
public DatabaseImpl(Connection connection, Options options) {
this.connection = connection;
this.options = options;
}
public DatabaseImpl(Flavor flavor) {
this(new OptionsDefault(flavor));
}
public DatabaseImpl(Options options) {
this.connection = null;
this.options = options;
}
@Override
public DatabaseImpl get() {
return this;
}
@Override
public SqlInsert toInsert(String sql) {
return new SqlInsertImpl(connection, sql, options);
}
@Override
public SqlInsert toInsert(Sql sql) {
return new SqlInsertImpl(connection, sql.sql(), options).apply(sql);
}
@Override
public SqlSelect toSelect(String sql) {
return new SqlSelectImpl(connection, sql, options);
}
@Override
public SqlSelect toSelect(Sql sql) {
return new SqlSelectImpl(connection, sql.sql(), options).apply(sql);
}
@Override
public SqlUpdate toUpdate(String sql) {
return new SqlUpdateImpl(connection, sql, options);
}
@Override
public SqlUpdate toUpdate(Sql sql) {
return new SqlUpdateImpl(connection, sql.sql(), options).apply(sql);
}
@Override
public SqlUpdate toDelete(String sql) {
return new SqlUpdateImpl(connection, sql, options);
}
@Override
public SqlUpdate toDelete(Sql sql) {
return new SqlUpdateImpl(connection, sql.sql(), options).apply(sql);
}
@Override
public Ddl ddl(String sql) {
return new DdlImpl(connection, sql, options);
}
@Override
public Long nextSequenceValue(/*@Untainted*/ String sequenceName) {
return toSelect(flavor().sequenceSelectNextVal(sequenceName)).queryLongOrNull();
}
@Override
public Date nowPerApp() {
return options.currentDate();
}
public void commitNow() {
if (options.ignoreTransactionControl()) {
log.fine("Ignoring call to commitNow()");
return;
}
if (!options.allowTransactionControl()) {
throw new DatabaseException("Calls to commitNow() are not allowed");
}
try {
connection.commit();
} catch (Exception e) {
throw new DatabaseException("Unable to commit transaction", e);
}
}
public void rollbackNow() {
if (options.ignoreTransactionControl()) {
log.fine("Ignoring call to rollbackNow()");
return;
}
if (!options.allowTransactionControl()) {
throw new DatabaseException("Calls to rollbackNow() are not allowed");
}
try {
connection.rollback();
} catch (Exception e) {
throw new DatabaseException("Unable to rollback transaction", e);
}
}
@Override
public Connection underlyingConnection() {
if (!options.allowConnectionAccess()) {
throw new DatabaseException("Calls to underlyingConnection() are not allowed");
}
return connection;
}
@Override
public Options options() {
return options;
}
@Override
public Flavor flavor() {
return options.flavor();
}
@Override
public When when() {
return new When(options.flavor());
}
@Override
public void dropSequenceQuietly(/*@Untainted*/ String sequenceName) {
ddl(flavor().sequenceDrop(sequenceName)).executeQuietly();
}
@Override
public void dropTableQuietly(/*@Untainted*/ String tableName) {
if (flavor() == Flavor.postgresql || flavor() == Flavor.hsqldb) {
ddl("drop table if exists " + tableName).executeQuietly();
} else {
ddl("drop table " + tableName).executeQuietly();
}
}
@Override
public boolean tableExists(String tableName) throws DatabaseException {
String schemaName = null;
Method getSchema = null;
try {
// Use reflections to see if connection.getSchema API exists. It should exist for any JDBC7 or later implementation
// We still support Oracle 11 with odbc6, however, so we can't assume it's there.
getSchema = connection.getClass().getDeclaredMethod("getSchema");
} catch (NoSuchMethodException noMethodExc) {
// Expected if method does not exist - just let it go
}
try {
if (getSchema != null) {
schemaName = ((String) getSchema.invoke(connection, new Object[0]));
} else if (flavor() == Flavor.oracle) {
// Oracle defaults to user name schema - use that.
log.warning("Connection getSchema API was not found. Defaulting to Oracle user name schema." +
"If this is not appropriate, please use tableExists(tableName, schemaName) API or upgrade to ojdbc7 or later");
schemaName = connection.getMetaData().getUserName();
}
if (schemaName == null) {
// connection.getSchema API was supported starting at JDK1.7. Method should not be null.
throw new NullPointerException("Unable to retrieve schema name.");
}
} catch (Exception exc) {
throw new DatabaseException("Unable to determine the schema. " +
"Please use tableExists(tableName, schemaName API) or upgrade to a JDBC7 driver or later.", exc);
}
return tableExists(tableName, schemaName);
}
@Override
public boolean tableExists(String tableName, String schemaName) throws DatabaseException {
if (tableName != null) {
try {
DatabaseMetaData metadata = connection.getMetaData();
String normalizedTable = normalizeTableName(tableName);
ResultSet resultSet =
metadata.getTables(connection.getCatalog(), schemaName, normalizedTable, new String[]{"TABLE", "VIEW"});
while (resultSet.next()) {
if (normalizedTable.equals(resultSet.getString("TABLE_NAME"))) {
return true;
}
}
} catch (SQLException exc) {
throw new DatabaseException("Unable to look up table " + tableName
+ " in schema " + schemaName + " : " + exc.getMessage(),
exc);
}
}
return false;
}
@Override
public String normalizeTableName(String tableName) {
if (tableName == null) {
return tableName;
}
// If user gave us a quoted string, leave it alone for look up
if (tableName.length() > 2) {
if (tableName.startsWith("\"") && tableName.endsWith("\"")) {
// Remove quotes and return as is.
return tableName.substring(1, tableName.length() - 1);
}
}
if (flavor().isNormalizedUpperCase()) {
return tableName.toUpperCase();
}
return tableName.toLowerCase();
}
@Override
public void assertTimeSynchronized(long millisToWarn, long millisToError) {
toSelect("select ?" + flavor().fromAny())
.argDateNowPerDb().queryFirstOrNull(r -> {
Date appDate = nowPerApp();
Date dbDate = r.getDateOrNull();
if (dbDate == null) {
throw new DatabaseException("Expecting a date in the result");
}
if (Math.abs(appDate.getTime() - dbDate.getTime()) > 3600000) {
throw new DatabaseException("App and db time are over an hour apart (check your timezones) app: "
+ DateTimeFormatter.ISO_INSTANT.format(appDate.toInstant()) + " db: "
+ DateTimeFormatter.ISO_INSTANT.format(dbDate.toInstant()));
}
if (Math.abs(appDate.getTime() - dbDate.getTime()) > millisToError) {
throw new DatabaseException("App and db time over " + millisToError + " millis apart (check your clocks) app: "
+ DateTimeFormatter.ISO_INSTANT.format(appDate.toInstant()) + " db: "
+ DateTimeFormatter.ISO_INSTANT.format(dbDate.toInstant()));
}
if (Math.abs(appDate.getTime() - dbDate.getTime()) > millisToWarn) {
log.warning("App and db time are over " + millisToWarn + " millis apart (check your clocks) app: "
+ DateTimeFormatter.ISO_INSTANT.format(appDate.toInstant()) + " db: "
+ DateTimeFormatter.ISO_INSTANT.format(dbDate.toInstant()));
}
return null;
});
}
@Override
public void assertTimeSynchronized() {
assertTimeSynchronized(10000, 30000);
}
}

@ -0,0 +1,18 @@
package org.xbib.jdbc.query;
import java.util.function.Supplier;
/**
* A block of runnable code using a transacted Database.
*/
public interface DbCode {
/**
* Implement this method to provide a block of code that uses the provided database
* and is transacted. Whether the transaction will commit or rollback is typically
* controlled by the code that invokes this method.
*
* <p>If a {@link Throwable} is thrown from this method, it will be caught, wrapped in
* a DatabaseException (if it is not already one), and then propagated.</p>
*/
void run(Supplier<Database> dbs) throws Exception;
}

@ -0,0 +1,19 @@
package org.xbib.jdbc.query;
import java.util.function.Supplier;
/**
* A block of runnable code using a transacted Database.
*/
@FunctionalInterface
public interface DbCodeTx {
/**
* Implement this method to provide a block of code that uses the provided database
* and is transacted. Whether the transaction will commit or rollback is typically
* controlled by the code that invokes this method.
*
* <p>If a {@link Throwable} is thrown from this method, it will be caught, wrapped in
* a DatabaseException (if it is not already one), and then propagated.</p>
*/
void run(Supplier<Database> db, Transaction tx) throws Exception;
}

@ -0,0 +1,19 @@
package org.xbib.jdbc.query;
import java.util.function.Supplier;
/**
* A block of runnable code using a transacted Database.
*/
@FunctionalInterface
public interface DbCodeTyped<T> {
/**
* Implement this method to provide a block of code that uses the provided database
* and is transacted. Whether the transaction will commit or rollback is typically
* controlled by the code that invokes this method.
*
* <p>If a {@link Throwable} is thrown from this method, it will be caught, wrapped in
* a DatabaseException (if it is not already one), and then propagated.</p>
*/
T run(Supplier<Database> dbs) throws Exception;
}

@ -0,0 +1,19 @@
package org.xbib.jdbc.query;
import java.util.function.Supplier;
/**
* A block of runnable code using a transacted Database.
*/
@FunctionalInterface
public interface DbCodeTypedTx<T> {
/**
* Implement this method to provide a block of code that uses the provided database
* and is transacted. Whether the transaction will commit or rollback is typically
* controlled by the code that invokes this method.
*
* <p>If a {@link Throwable} is thrown from this method, it will be caught, wrapped in
* a DatabaseException (if it is not already one), and then propagated.</p>
*/
T run(Supplier<Database> db, Transaction tx) throws Exception;
}

@ -0,0 +1,19 @@
package org.xbib.jdbc.query;
/**
* Interface for executing a chunk of DDL within the database.
*/
public interface Ddl {
/**
* Execute the DDL statement. All checked SQLExceptions get wrapped in DatabaseExceptions.
*/
void execute();
/**
* This just does an execute() call and silently discards any DatabaseException
* that might occur. This can be useful for things like drop statements, where
* some databases don't make it easy to conditionally drop things only if they
* exist.
*/
void executeQuietly();
}

@ -0,0 +1,97 @@
package org.xbib.jdbc.query;
import org.xbib.jdbc.query.util.DebugSql;
import org.xbib.jdbc.query.util.Metric;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
public class DdlImpl implements Ddl {
private static final Logger log = Logger.getLogger(Database.class.getName());
private static final Logger logQuiet = Logger.getLogger(Database.class.getName() + ".quiet");
private final Connection connection;
private final String sql;
private final Options options;
DdlImpl(Connection connection, String sql, Options options) {
this.connection = connection;
this.sql = sql;
this.options = options;
}
private void updateInternal(boolean quiet) {
CallableStatement ps = null;
Metric metric = new Metric(log.isLoggable(Level.FINE));
boolean isSuccess = false;
String errorCode = null;
Exception logEx = null;
try {
ps = connection.prepareCall(sql);
metric.checkpoint("prep");
ps.execute();
metric.checkpoint("exec");
isSuccess = true;
} catch (Exception e) {
errorCode = options.generateErrorCode();
logEx = e;
throw DatabaseException.wrap(DebugSql.exceptionMessage(sql, null, errorCode, options), e);
} finally {
close(ps);
metric.checkpoint("close");
// PostgreSQL requires explicit commit since we are running with setAutoCommit(false)
commit(connection);
metric.done("commit");
if (isSuccess) {
DebugSql.logSuccess("DDL", log, metric, sql, null, options);
} else if (quiet) {
DebugSql.logWarning("DDL", logQuiet, metric, errorCode, sql, null, options, logEx);
} else {
DebugSql.logError("DDL", log, metric, errorCode, sql, null, options, logEx);
}
}
}
@Override
public void execute() {
updateInternal(false);
}
@Override
public void executeQuietly() {
try {
updateInternal(true);
} catch (DatabaseException e) {
// Ignore, as requested
}
}
private void close(Statement s) {
if (s != null) {
try {
s.close();
} catch (Exception e) {
log.log(Level.SEVERE, "Caught exception closing the Statement", e);
}
}
}
private void commit(Connection c) {
if (c != null) {
try {
c.commit();
} catch (Exception e) {
log.log(Level.SEVERE, "Caught exception on commit", e);
}
}
}
}

@ -0,0 +1,993 @@
package org.xbib.jdbc.query;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
/**
* Enumeration of supported databases with various compatibility settings.
*/
public enum Flavor {
derby {
@Override
public boolean isNormalizedUpperCase() {
return true;
}
@Override
public String typeInteger() {
return "integer";
}
@Override
public String typeBoolean() {
return "char(1)";
}
@Override
public String typeLong() {
return "bigint";
}
@Override
public String typeFloat() {
return "real";
}
@Override
public String typeDouble() {
return "double";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")";
}
@Override
public String typeStringVar(int length) {
return "varchar(" + length + ")";
}
@Override
public String typeStringFixed(int length) {
return "char(" + length + ")";
}
@Override
public String typeClob() {
return "clob";
}
@Override
public String typeBlob() {
return "blob";
}
@Override
public String typeDate() {
return "timestamp";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return false;
}
@Override
public boolean useBytesForBlob() {
return false;
}
@Override
public String sequenceNextVal(String sequenceName) {
return "next value for " + sequenceName;
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
return "values next value for " + sequenceName;
}
@Override
public String sequenceDrop(String dbtestSeq) {
return "drop sequence " + dbtestSeq + " restrict";
}
@Override
public boolean supportsInsertReturning() {
return false;
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
return "";
}
@Override
public String sequenceOrderClause(boolean order) {
return "";
}
@Override
public String sequenceCycleClause(boolean cycle) {
return cycle ? " cycle" : " no cycle";
}
@Override
public String dbTimeMillis() {
return "current_timestamp";
}
@Override
public String fromAny() {
return " from sysibm.sysdummy1";
}
@Override
public String dateAsSqlFunction(Date date, Calendar calendar) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
dateFormat.setCalendar(calendar);
return "timestamp('" + dateFormat.format(date) + "')";
}
@Override
public String localDateAsSqlFunction(Date date) {
return "'" + date.toString() + "'";
}
@Override
public String sequenceOptions() {
return " as bigint";
}
@Override
public boolean autoCommitOnly() {
return false;
}
},
sqlserver {
@Override
public boolean isNormalizedUpperCase() {
return false;
}
@Override
public String typeFloat() {
return "float(24)";
}
@Override
public String typeDouble() {
return "float(53)";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")";
}
@Override
public String typeInteger() {
return "numeric(10)";
}
@Override
public String typeBoolean() {
return "char(1)";
}
@Override
public String typeLong() {
return "numeric(19)";
}
@Override
public String typeDate() {
return "datetime2(3)";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return false;
}
@Override
public boolean useBytesForBlob() {
return false;
}
@Override
public String sequenceNextVal(String sequenceName) {
return "next value for " + sequenceName;
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
return "select next value for " + sequenceName;
}
@Override
public String sequenceDrop(String dbtestSeq) {
return "drop sequence " + dbtestSeq;
}
@Override
public String typeStringVar(int length) {
return "varchar(" + length + ")";
}
@Override
public String typeStringFixed(int length) {
return "char(" + length + ")";
}
@Override
public String typeClob() {
return "varchar(max)";
}
@Override
public String typeBlob() {
return "varbinary(max)";
}
@Override
public String sequenceOrderClause(boolean order) {
// Not supported
return "";
}
@Override
public String sequenceCycleClause(boolean cycle) {
return cycle ? " cycle" : " no cycle";
}
@Override
public boolean supportsInsertReturning() {
// TODO it probably does, but I haven't figure it out yet
return false;
}
@Override
public String dbTimeMillis() {
return "current_timestamp";
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
if (nbrValuesToCache < 2) {
return " no cache";
}
return " cache " + nbrValuesToCache;
}
@Override
public String fromAny() {
return "";
}
@Override
public String dateAsSqlFunction(Date date, Calendar calendar) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
dateFormat.setCalendar(calendar);
return "cast('" + dateFormat.format(date) + "' as datetime2(3))";
}
@Override
public String localDateAsSqlFunction(Date date) {
return "'" + date.toString() + "'";
}
@Override
public String sequenceOptions() {
return "";
}
@Override
public boolean autoCommitOnly() {
return false;
}
},
oracle {
@Override
public boolean isNormalizedUpperCase() {
return true;
}
@Override
public String typeFloat() {
return "binary_float";
}
@Override
public String typeDouble() {
return "binary_double";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")";
}
@Override
public String typeInteger() {
return "numeric(10)";
}
@Override
public String typeBoolean() {
return "char(1 char)";
}
@Override
public String typeLong() {
return "numeric(19)";
}
@Override
public String typeDate() {
return "timestamp(3)";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return false;
}
@Override
public boolean useBytesForBlob() {
return false;
}
@Override
public String sequenceNextVal(String sequenceName) {
return sequenceName + ".nextval";
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
return "select " + sequenceName + ".nextval from dual";
}
@Override
public String sequenceDrop(String dbtestSeq) {
return "drop sequence " + dbtestSeq;
}
@Override
public String typeStringVar(int length) {
return "varchar2(" + length + " char)";
}
@Override
public String typeStringFixed(int length) {
return "char(" + length + " char)";
}
@Override
public String typeClob() {
return "clob";
}
@Override
public String typeBlob() {
return "blob";
}
@Override
public String sequenceOrderClause(boolean order) {
return order ? " order" : " noorder";
}
@Override
public String sequenceCycleClause(boolean cycle) {
return cycle ? " cycle" : " nocycle";
}
@Override
public boolean supportsInsertReturning() {
return true;
}
@Override
public String dbTimeMillis() {
return "systimestamp(3)";
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
if (nbrValuesToCache < 2) {
return " nocache";
}
return " cache " + nbrValuesToCache;
}
@Override
public String fromAny() {
return " from dual";
}
@Override
public String dateAsSqlFunction(Date date, Calendar calendar) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
dateFormat.setCalendar(calendar);
return "timestamp '" + dateFormat.format(date) + "'";
}
@Override
public String localDateAsSqlFunction(Date date) {
return "to_date('" + date.toString() + "', 'yyyy-mm-dd')";
}
@Override
public String sequenceOptions() {
return "";
}
@Override
public boolean autoCommitOnly() {
return false;
}
},
postgresql {
@Override
public boolean isNormalizedUpperCase() {
return false;
}
@Override
public String typeInteger() {
return "integer";
}
@Override
public String typeBoolean() {
return "char(1)";
}
@Override
public String typeLong() {
return "bigint";
}
@Override
public String typeFloat() {
return "real";
}
@Override
public String typeDouble() {
return "double precision";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")";
}
@Override
public String typeStringVar(int length) {
return "varchar(" + length + ")";
}
@Override
public String typeStringFixed(int length) {
return "char(" + length + ")";
}
@Override
public String typeClob() {
return "text";
}
@Override
public String typeBlob() {
return "bytea";
}
@Override
public String typeDate() {
return "timestamp(3)";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return true;
}
@Override
public boolean useBytesForBlob() {
return true;
}
@Override
public String sequenceNextVal(String sequenceName) {
return "nextval('" + sequenceName + "')";
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
return "select nextval('" + sequenceName + "')";
}
@Override
public String sequenceDrop(String dbtestSeq) {
return "drop sequence " + dbtestSeq;
}
@Override
public String sequenceOrderClause(boolean order) {
return "";
}
@Override
public String sequenceCycleClause(boolean cycle) {
return cycle ? " cycle" : " no cycle";
}
@Override
public String fromAny() {
return "";
}
@Override
public boolean supportsInsertReturning() {
return true;
}
@Override
public String dbTimeMillis() {
return "date_trunc('milliseconds',localtimestamp)";
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
return " cache " + nbrValuesToCache;
}
@Override
public String dateAsSqlFunction(Date date, Calendar calendar) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
dateFormat.setCalendar(calendar);
return "'" + dateFormat.format(date) + " GMT'::timestamp";
}
@Override
public String localDateAsSqlFunction(Date date) {
return "'" + date.toString() + "'";
}
@Override
public String sequenceOptions() {
return "";
}
@Override
public boolean autoCommitOnly() {
return false;
}
},
hsqldb {
@Override
public boolean isNormalizedUpperCase() {
return true;
}
@Override
public String typeInteger() {
return "integer";
}
@Override
public String typeBoolean() {
return "char(1)";
}
@Override
public String typeLong() {
return "bigint";
}
@Override
public String typeFloat() {
return "double";
}
@Override
public String typeDouble() {
return "double";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")";
}
@Override
public String typeStringVar(int length) {
return "varchar(" + length + ")";
}
@Override
public String typeStringFixed(int length) {
return "char(" + length + ")";
}
@Override
public String typeClob() {
return "clob(2G)";
}
@Override
public String typeBlob() {
return "blob(2G)";
}
@Override
public String typeDate() {
return "timestamp(3)";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return true;
}
@Override
public boolean useBytesForBlob() {
return true;
}
@Override
public String sequenceNextVal(String sequenceName) {
return "next value for " + sequenceName + "";
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
return "select " + sequenceNextVal(sequenceName) + fromAny();
}
@Override
public String sequenceDrop(String dbtestSeq) {
return "drop sequence if exists " + dbtestSeq;
}
@Override
public String sequenceOrderClause(boolean order) {
return "";
}
@Override
public String sequenceCycleClause(boolean cycle) {
return cycle ? " cycle" : " no cycle";
}
@Override
public String fromAny() {
return " from (values(0))";
}
@Override
public boolean supportsInsertReturning() {
return false;
}
@Override
public String dbTimeMillis() {
return "localtimestamp";
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
return "";
}
@Override
public String dateAsSqlFunction(Date date, Calendar calendar) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000XXX");
dateFormat.setCalendar(calendar);
return "cast(timestamp '" + dateFormat.format(date) + "' as timestamp without time zone)";
}
@Override
public String localDateAsSqlFunction(Date date) {
return "'" + date.toString() + "'";
}
@Override
public String sequenceOptions() {
return " as bigint";
}
@Override
public boolean autoCommitOnly() {
return false;
}
},
bigquery {
@Override
public boolean isNormalizedUpperCase() {
return false;
}
@Override
public String typeInteger() {
return "int64";
}
@Override
public String typeBoolean() {
// BigQuery has a native boolean type, but we're not trying to use it
return "string";
}
@Override
public String typeLong() {
return "int64";
}
@Override
public String typeFloat() {
return "float64";
}
@Override
public String typeDouble() {
return "float64";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric";
}
@Override
public String typeStringVar(int length) {
return "string";
}
@Override
public String typeStringFixed(int length) {
return "string";
}
@Override
public String typeClob() {
return "string";
}
@Override
public String typeBlob() {
return "bytes";
}
@Override
public String typeDate() {
return "datetime";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return true;
}
@Override
public boolean useBytesForBlob() {
return true;
}
@Override
public String sequenceNextVal(String sequenceName) {
throw new UnsupportedOperationException();
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
throw new UnsupportedOperationException();
}
@Override
public String sequenceDrop(String dbtestSeq) {
throw new UnsupportedOperationException();
}
@Override
public boolean supportsInsertReturning() {
return false;
}
@Override
public String dbTimeMillis() {
return "current_timestamp()";
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
throw new UnsupportedOperationException();
}
@Override
public String sequenceOrderClause(boolean order) {
throw new UnsupportedOperationException();
}
@Override
public String sequenceCycleClause(boolean cycle) {
throw new UnsupportedOperationException();
}
@Override
public String fromAny() {
return "";
}
@Override
public String dateAsSqlFunction(Date date, Calendar calendar) {
// Construct a datetime literal
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
dateFormat.setCalendar(calendar);
return String.format("datetime '%s'", dateFormat.format(date));
}
@Override
public String localDateAsSqlFunction(Date date) {
// Construct a datetime literal
return String.format("datetime '%s'", date.toString());
}
@Override
public String sequenceOptions() {
throw new UnsupportedOperationException();
}
@Override
public boolean autoCommitOnly() {
return true;
}
};
public static Flavor fromJdbcUrl(String url) {
if (url == null) {
throw new DatabaseException("url must not be null");
}
if (url.startsWith("jdbc:postgresql:")) {
return postgresql;
} else if (url.startsWith("jdbc:oracle:")) {
return oracle;
} else if (url.startsWith("jdbc:sqlserver:")) {
return sqlserver;
} else if (url.startsWith("jdbc:hsqldb:")) {
return hsqldb;
} else if (url.startsWith("jdbc:derby:")) {
return derby;
} else if (url.startsWith("jdbc:bigquery:")) {
return bigquery;
} else {
throw new DatabaseException("Cannot determine database flavor from url");
}
}
public static String driverForJdbcUrl(String url) {
if (url == null) {
throw new DatabaseException("url must not be null");
}
if (url.startsWith("jdbc:postgresql:")) {
return "org.postgresql.Driver";
} else if (url.startsWith("jdbc:oracle:")) {
return "oracle.jdbc.OracleDriver";
} else if (url.startsWith("jdbc:sqlserver:")) {
return "com.microsoft.sqlserver.jdbc.SQLServerDriver";
} else if (url.startsWith("jdbc:hsqldb:")) {
return "org.hsqldb.jdbc.JDBCDriver";
} else if (url.startsWith("jdbc:derby:")) {
return "org.apache.derby.jdbc.EmbeddedDriver";
} else if (url.startsWith("jdbc:bigquery:")) {
return "com.simba.googlebigquery.jdbc42.Driver";
} else {
throw new DatabaseException("Cannot determine database driver class from url");
}
}
// Returns true if DB normalizes to upper case names for ids like tables and columns
// See https://github.com/ontop/ontop/wiki/Case-sensitivity-for-SQL-identifiers
public abstract boolean isNormalizedUpperCase();
public abstract String typeInteger();
public abstract String typeBoolean();
public abstract String typeLong();
public abstract String typeFloat();
public abstract String typeDouble();
public abstract String typeBigDecimal(int size, int precision);
public abstract String typeStringVar(int length);
public abstract String typeStringFixed(int length);
public abstract String typeClob();
public abstract String typeBlob();
public abstract String typeDate();
public abstract String typeLocalDate();
public abstract boolean useStringForClob();
public abstract boolean useBytesForBlob();
public abstract String sequenceNextVal(String sequenceName);
public abstract String sequenceSelectNextVal(String sequenceName);
public abstract String sequenceDrop(String dbtestSeq);
public abstract boolean supportsInsertReturning();
public abstract String dbTimeMillis();
public abstract String sequenceCacheClause(int nbrValuesToCache);
public abstract String sequenceOrderClause(boolean order);
public abstract String sequenceCycleClause(boolean cycle);
/**
* Indicate what should follow a constant select statement. For example, "select 1"
* works on some databases, while Oracle requires "select 1 from dual". For Oracle
* this function should return " from dual" (including the leading space).
*/
public abstract String fromAny();
/**
* Return a SQL function representing the specified date. For example, in PostgreSQL this
* looks like "'1970-01-02 02:17:36.789000 GMT'::timestamp".
*/
public abstract String dateAsSqlFunction(Date date, Calendar calendar);
/**
* Return a SQL function representing the specified date without time.
*/
public abstract String localDateAsSqlFunction(Date date);
public abstract String sequenceOptions();
public abstract boolean autoCommitOnly();
}

@ -0,0 +1,133 @@
package org.xbib.jdbc.query;
import org.xbib.jdbc.query.util.RewriteArg;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Convenience class to allow use of (:mylabel) for SQL parameters in addition to
* positional (?) parameters. This doesn't do any smart parsing of the SQL, it is just
* looking for ':' and '?' characters. If the SQL needs to include an actual ':' or '?'
* character, use two of them ('::' or '??'), and they will be replaced with a
* single ':' or '?'.
*/
public class MixedParameterSql {
private final String sqlToExecute;
private final Object[] args;
public MixedParameterSql(String sql, List<Object> positionalArgs, Map<String, Object> nameToArg) {
if (positionalArgs == null) {
positionalArgs = new ArrayList<>();
}
if (nameToArg == null) {
nameToArg = new HashMap<>();
}
StringBuilder newSql = new StringBuilder(sql.length());
List<String> argNamesList = new ArrayList<>();
List<String> rewrittenArgs = new ArrayList<>();
List<Object> argsList = new ArrayList<>();
int searchIndex = 0;
int currentPositionalArg = 0;
while (searchIndex < sql.length()) {
int nextColonIndex = sql.indexOf(':', searchIndex);
int nextQmIndex = sql.indexOf('?', searchIndex);
if (nextColonIndex < 0 && nextQmIndex < 0) {
newSql.append(sql.substring(searchIndex));
break;
}
if (nextColonIndex >= 0 && (nextQmIndex == -1 || nextColonIndex < nextQmIndex)) {
// The next parameter we found is a named parameter (":foo")
if (nextColonIndex > sql.length() - 2) {
// Probably illegal sql, but handle boundary condition
break;
}
// Allow :: as escape for :
if (sql.charAt(nextColonIndex + 1) == ':') {
newSql.append(sql, searchIndex, nextColonIndex + 1);
searchIndex = nextColonIndex + 2;
continue;
}
int endOfNameIndex = nextColonIndex + 1;
while (endOfNameIndex < sql.length() && Character.isJavaIdentifierPart(sql.charAt(endOfNameIndex))) {
endOfNameIndex++;
}
newSql.append(sql, searchIndex, nextColonIndex);
String paramName = sql.substring(nextColonIndex + 1, endOfNameIndex);
boolean secretParam = paramName.startsWith("secret");
Object arg = nameToArg.get(paramName);
if (arg instanceof RewriteArg) {
newSql.append(((RewriteArg) arg).getSql());
rewrittenArgs.add(paramName);
} else {
newSql.append('?');
if (nameToArg.containsKey(paramName)) {
argsList.add(secretParam ? new SecretArg(arg) : arg);
} else {
throw new DatabaseException("The SQL requires parameter ':" + paramName + "' but no value was provided");
}
argNamesList.add(paramName);
}
searchIndex = endOfNameIndex;
} else {
// The next parameter we found is a positional parameter ("?")
// Allow ?? as escape for ?
if (nextQmIndex < sql.length() - 1 && sql.charAt(nextQmIndex + 1) == '?') {
newSql.append(sql, searchIndex, nextQmIndex + 1);
searchIndex = nextQmIndex + 2;
continue;
}
newSql.append(sql, searchIndex, nextQmIndex);
if (currentPositionalArg >= positionalArgs.size()) {
throw new DatabaseException("Not enough positional parameters (" + positionalArgs.size() + ") were provided");
}
if (positionalArgs.get(currentPositionalArg) instanceof RewriteArg) {
newSql.append(((RewriteArg) positionalArgs.get(currentPositionalArg)).getSql());
} else {
newSql.append('?');
argsList.add(positionalArgs.get(currentPositionalArg));
}
currentPositionalArg++;
searchIndex = nextQmIndex + 1;
}
}
this.sqlToExecute = newSql.toString();
args = argsList.toArray(new Object[argsList.size()]);
// Sanity check number of arguments to provide a better error message
if (currentPositionalArg != positionalArgs.size()) {
throw new DatabaseException("Wrong number of positional parameters were provided (expected: "
+ currentPositionalArg + ", actual: " + positionalArgs.size() + ")");
}
if (nameToArg.size() > args.length - Math.max(0, positionalArgs.size() - 1) + rewrittenArgs.size()) {
Set<String> unusedNames = new HashSet<>(nameToArg.keySet());
unusedNames.removeAll(argNamesList);
unusedNames.removeAll(rewrittenArgs);
if (!unusedNames.isEmpty()) {
throw new DatabaseException("These named parameters do not exist in the query: " + unusedNames);
}
}
}
public String getSqlToExecute() {
return sqlToExecute;
}
public Object[] getArgs() {
return args;
}
}

@ -0,0 +1,130 @@
package org.xbib.jdbc.query;
import java.util.Calendar;
import java.util.Date;
/**
* Control various optional behavior for the database interactions.
*/
public interface Options {
/**
* Control whether the Database object will allow calls to commitNow()
* and rollbackNow(). By default it will throw exceptions if you try to
* call those.
*/
boolean allowTransactionControl();
/**
* Useful for testing code that explicitly controls transactions, and you
* don't really want it to commit/rollback. Disabled by default, meaning
* calls will be allowed or throw exceptions depending on allowTransctionControl().
* The value of allowTranscationControl() has no affect if this returns true.
*/
boolean ignoreTransactionControl();
/**
* Control whether the Database object will allow calls to underlyingConnection().
* By default that method will throw an exception.
*/
boolean allowConnectionAccess();
/**
* If this is false, log messages will look something like:
*
* <pre>
* ...select a from b where c=?
* </pre>
* <p>
* If this is true, log messages will look something like:
*
* <pre>
* ...select a from b where c=?|select a from b where c='abc'
* </pre>
*
* @return true if parameter values should be logged along with SQL, false otherwise
*/
boolean isLogParameters();
/**
* If true, text of the SQL and possibly parameter values (depending on @{#isLogParameters()})
* will be included in exception messages. This can be very helpful for debugging, but poses
* some disclosure risks.
*
* @return true to add possibly sensitive data in exception messages, false otherwise
*/
boolean isDetailedExceptions();
/**
* In cases where exceptions are thrown, use this method to provide a common
* code that will be included in the exception message and the log message
* so they can be searched and correlated later.
*
* @return an arbitrary, fairly unique, speakable over the phone, without whitespace
*/
String generateErrorCode();
/**
* Indicate whether to use the Blob functionality of the underlying database driver,
* or whether to use setBytes() methods instead. Using Blobs is preferred, but is not
* supported by all drivers.
*
* <p>The default behavior of this method is to delegate to flavor().useBytesForBlob(),
* but it is provided on this interface so the behavior can be controlled.
*
* @return true to avoid using Blob functionality, false otherwise
*/
boolean useBytesForBlob();
/**
* Indicate whether to use the Clob functionality of the underlying database driver,
* or whether to use setString() methods instead. Using Clobs is preferred, but is not
* supported by all drivers.
*
* <p>The default behavior of this method is to delegate to flavor().useStringForClob(),
* but it is provided on this interface so the behavior can be controlled.
*
* @return true to avoid using Clob functionality, false otherwise
*/
boolean useStringForClob();
/**
* Access compatibility information for the underlying database. The
* Flavor class enumerates the known databases and tries to smooth over
* some of the variations in features and syntax.
*/
Flavor flavor();
/**
* The value returned by this method will be used for argDateNowPerApp() calls. It
* may also be used for argDateNowPerDb() calls if you have enabled that.
*/
Date currentDate();
/**
* Wherever argDateNowPerDb() is specified, use argDateNowPerApp() instead. This is
* useful for testing purposes as you can use OptionsOverride to provide your
* own system clock that will be used for time travel.
*/
boolean useDatePerAppOnly();
/**
* This calendar will be used for conversions when storing and retrieving timestamps
* from the database. By default this is the JVM default with TimeZone explicitly set
* to GMT (so timestamps will be stored in the database as GMT).
*
* <p>It is strongly recommended to always run your database in GMT timezone, and
* leave this set to the default.</p>
*
* <p>Behavior in releases 1.3 and prior was to use the JVM default TimeZone, and
* this was not configurable.</p>
*/
Calendar calendarForTimestamps();
/**
* The maximum number of characters to print in debug SQL for a given String type
* insert/update/query parameter. If it exceeds this length, the parameter value
* will be truncated at the max and a "..." will be appended. Note this affects
* both {@code argString()} and {@code argClobString()} methods.
*/
int maxStringLengthParam();
}

@ -0,0 +1,84 @@
package org.xbib.jdbc.query;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
/**
* Control various optional behavior for the database interactions.
*/
public class OptionsDefault implements Options {
private final Flavor flavor;
public OptionsDefault(Flavor flavor) {
this.flavor = flavor;
}
@Override
public boolean allowTransactionControl() {
return false;
}
@Override
public boolean ignoreTransactionControl() {
return false;
}
@Override
public boolean allowConnectionAccess() {
return false;
}
@Override
public boolean isLogParameters() {
return false;
}
@Override
public boolean isDetailedExceptions() {
return false;
}
@Override
public String generateErrorCode() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd:H:m:s");
return sdf.format(new Date()) + "-" + Math.round(Math.random() * 1000000);
}
@Override
public boolean useBytesForBlob() {
return flavor().useBytesForBlob();
}
@Override
public boolean useStringForClob() {
return flavor().useStringForClob();
}
@Override
public Flavor flavor() {
return flavor;
}
@Override
public Date currentDate() {
return new Date();
}
@Override
public boolean useDatePerAppOnly() {
return false;
}
@Override
public Calendar calendarForTimestamps() {
return Calendar.getInstance(TimeZone.getDefault());
}
@Override
public int maxStringLengthParam() {
return 4000;
}
}

@ -0,0 +1,108 @@
package org.xbib.jdbc.query;
import java.util.Calendar;
import java.util.Date;
/**
* Base class for selectively overriding another Options object.
*/
public class OptionsOverride implements Options {
private Options parent;
/**
* Wrap another {@code Options} and defer to it for anything we choose not
* to override.
*/
public OptionsOverride(Options parent) {
this.parent = parent;
}
/**
* Defer to OptionsDefault for anything that is not specified, and use postgresql flavor.
*/
public OptionsOverride() {
parent = new OptionsDefault(Flavor.postgresql);
}
/**
* Defer to OptionsDefault for anything that is not specified, using the specified flavor.
*/
public OptionsOverride(Flavor flavor) {
parent = new OptionsDefault(flavor);
}
public void setParent(Options parent) {
this.parent = parent;
}
public OptionsOverride withParent(Options parent) {
this.parent = parent;
return this;
}
@Override
public boolean allowTransactionControl() {
return parent.allowTransactionControl();
}
@Override
public boolean ignoreTransactionControl() {
return parent.ignoreTransactionControl();
}
@Override
public boolean allowConnectionAccess() {
return parent.allowConnectionAccess();
}
@Override
public boolean isLogParameters() {
return parent.isLogParameters();
}
@Override
public boolean isDetailedExceptions() {
return parent.isDetailedExceptions();
}
@Override
public String generateErrorCode() {
return parent.generateErrorCode();
}
@Override
public boolean useBytesForBlob() {
return parent.useBytesForBlob();
}
@Override
public boolean useStringForClob() {
return parent.useStringForClob();
}
@Override
public Flavor flavor() {
return parent.flavor();
}
@Override
public Date currentDate() {
return parent.currentDate();
}
@Override
public boolean useDatePerAppOnly() {
return parent.useDatePerAppOnly();
}
@Override
public Calendar calendarForTimestamps() {
return parent.calendarForTimestamps();
}
@Override
public int maxStringLengthParam() {
return parent.maxStringLengthParam();
}
}

@ -0,0 +1,13 @@
package org.xbib.jdbc.query;
/**
* Thrown when a query is interrupted because a timeout was exceeded or it was
* explicitly cancelled.
*/
@SuppressWarnings("serial")
public class QueryTimedOutException extends DatabaseException {
public QueryTimedOutException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,435 @@
package org.xbib.jdbc.query;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.sql.ResultSetMetaData;
import java.time.LocalDate;
import java.util.Date;
/**
* Interface for reading results from a database query.
*/
public interface Row {
/**
* Obtain the names of the columns in the database. You probably want to
* avoid this method if possible, as the way column names are handled varies
* by database and driver. For example, Derby and Oracle normally convert
* column names to uppercase, while PostgreSQL normally converts column
* names to lowercase. If you do use this method, you might want to either
* call toUppercase()/toLowercase() or ensure the SQL explicitly specifies
* parameters with AS "FOO" (including quotes) to ensure your desired name
* will be honored.
*/
String[] getColumnLabels();
/**
* Get raw access to the underlying JDBC metadata.
*/
ResultSetMetaData getMetadata();
/**
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
* typically in a {@code CHAR(1)} column. This reads the value and converts it
* to {@code Boolean} or {@code null} as appropriate.
*
* <p>This is a short-hand method that reads columns in order, starting
* with the first, and automatically incrementing the column index.</p>
*
* <p>If you call one of the methods using an explicit column index or column name before
* calling this method, it will pick up at the next column following the explicit one.
* For example:</p>
*
* <pre>
* getX(); // column 1
* getX(5); // or getX("foo") if foo is column 5
* getX(); // column 6
* </pre>
*
* @return true if the value was "Y", false if it was "N", or null
* @throws DatabaseException if the value was something other than Y, N, or null
*/
Boolean getBooleanOrNull();
/**
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
* typically in a {@code CHAR(1)} column. This reads the value and converts it
* to {@code Boolean} or {@code null} as appropriate.
*
* @param columnOneBased column number to read (1 is the first column)
* @return true if the value was "Y", false if it was "N", or null
* @throws DatabaseException if the value was something other than Y, N, or null
*/
Boolean getBooleanOrNull(int columnOneBased);
/**
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
* typically in a {@code CHAR(1)} column. This reads the value and converts it
* to {@code Boolean} or {@code null} as appropriate.
*
* @param columnName SQL alias of the column to read (use all lowercase)
* @return true if the value was "Y", false if it was "N", or null
* @throws DatabaseException if the value was something other than Y, N, or null
*/
Boolean getBooleanOrNull(String columnName);
/**
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
* typically in a {@code CHAR(1)} column. This reads the value and converts it
* to a {@code boolean}. If the value is {@code null}, it will be converted to {@code false}.
*
* <p>This is a short-hand method that reads columns in order, starting
* with the first, and automatically incrementing the column index.</p>
*
* <p>If you call one of the methods using an explicit column index or column name before
* calling this method, it will pick up at the next column following the explicit one.
* For example:</p>
*
* <pre>
* getX(); // column 1
* getX(5); // or getX("foo") if foo is column 5
* getX(); // column 6
* </pre>
*
* @return true if the value was "Y", false if it was either "N" or null
* @throws DatabaseException if the value was something other than Y, N, or null
*/
boolean getBooleanOrFalse();
/**
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
* typically in a {@code CHAR(1)} column. This reads the value and converts it
* to a {@code boolean}. If the value is {@code null}, it will be converted to {@code false}.
*
* <p>This is a short-hand method that reads columns in order, starting
* with the first, and automatically incrementing the column index.</p>
*
* @param columnOneBased column number to read (1 is the first column)
* @return true if the value was "Y", false if it was either "N" or null
* @throws DatabaseException if the value was something other than Y, N, or null
*/
boolean getBooleanOrFalse(int columnOneBased);
/**
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
* typically in a {@code CHAR(1)} column. This reads the value and converts it
* to a {@code boolean}. If the value is {@code null}, it will be converted to {@code false}.
*
* <p>This is a short-hand method that reads columns in order, starting
* with the first, and automatically incrementing the column index.</p>
*
* @param columnName SQL alias of the column to read (use all lowercase)
* @return true if the value was "Y", false if it was either "N" or null
* @throws DatabaseException if the value was something other than Y, N, or null
*/
boolean getBooleanOrFalse(String columnName);
/**
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
* typically in a {@code CHAR(1)} column. This reads the value and converts it
* to a {@code boolean}. If the value is {@code null}, it will be converted to {@code true}.
*
* <p>This is a short-hand method that reads columns in order, starting
* with the first, and automatically incrementing the column index.</p>
*
* <p>If you call one of the methods using an explicit column index or column name before
* calling this method, it will pick up at the next column following the explicit one.
* For example:</p>
*
* <pre>
* getX(); // column 1
* getX(5); // or getX("foo") if foo is column 5
* getX(); // column 6
* </pre>
*
* @return true if the value was either "Y" or null, false if it was "N"
* @throws DatabaseException if the value was something other than Y, N, or null
*/
boolean getBooleanOrTrue();
/**
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
* typically in a {@code CHAR(1)} column. This reads the value and converts it
* to a {@code boolean}. If the value is {@code null}, it will be converted to {@code true}.
*
* <p>This is a short-hand method that reads columns in order, starting
* with the first, and automatically incrementing the column index.</p>
*
* @param columnOneBased column number to read (1 is the first column)
* @return true if the value was either "Y" or null, false if it was "N"
* @throws DatabaseException if the value was something other than Y, N, or null
*/
boolean getBooleanOrTrue(int columnOneBased);
/**
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
* typically in a {@code CHAR(1)} column. This reads the value and converts it
* to a {@code boolean}. If the value is {@code null}, it will be converted to {@code true}.
*
* <p>This is a short-hand method that reads columns in order, starting
* with the first, and automatically incrementing the column index.</p>
*
* @param columnName SQL alias of the column to read (use all lowercase)
* @return true if the value was either "Y" or null, false if it was "N"
* @throws DatabaseException if the value was something other than Y, N, or null
*/
boolean getBooleanOrTrue(String columnName);
Integer getIntegerOrNull();
Integer getIntegerOrNull(int columnOneBased);
Integer getIntegerOrNull(String columnName);
int getIntegerOrZero();
int getIntegerOrZero(int columnOneBased);
int getIntegerOrZero(String columnName);
Long getLongOrNull();
Long getLongOrNull(int columnOneBased);
Long getLongOrNull(String columnName);
long getLongOrZero();
long getLongOrZero(int columnOneBased);
long getLongOrZero(String columnName);
Float getFloatOrNull();
Float getFloatOrNull(int columnOneBased);
Float getFloatOrNull(String columnName);
float getFloatOrZero();
float getFloatOrZero(int columnOneBased);
float getFloatOrZero(String columnName);
Double getDoubleOrNull();
Double getDoubleOrNull(int columnOneBased);
Double getDoubleOrNull(String columnName);
double getDoubleOrZero();
double getDoubleOrZero(int columnOneBased);
double getDoubleOrZero(String columnName);
/**
* Note this method attempts to correct for "artifical" scale due to the database
* representation. Some databases will pad the number out to "full precision". This
* method tries to reduce scale if there is zero padding to the right of the decimal.
*/
BigDecimal getBigDecimalOrNull();
BigDecimal getBigDecimalOrNull(int columnOneBased);
BigDecimal getBigDecimalOrNull(String columnName);
BigDecimal getBigDecimalOrZero();
BigDecimal getBigDecimalOrZero(int columnOneBased);
BigDecimal getBigDecimalOrZero(String columnName);
/**
* @return the value, or null if it is SQL null; never returns the empty string
*/
String getStringOrNull();
/**
* @return the value, or null if it is SQL null; never returns the empty string
*/
String getStringOrNull(int columnOneBased);
/**
* @return the value, or null if it is SQL null; never returns the empty string
*/
String getStringOrNull(String columnName);
/**
* @return the value, or the empty string if it is SQL null; never returns null
*/
String getStringOrEmpty();
/**
* @return the value, or the empty string if it is SQL null; never returns null
*/
String getStringOrEmpty(int columnOneBased);
/**
* @return the value, or the empty string if it is SQL null; never returns null
*/
String getStringOrEmpty(String columnName);
/**
* @return the value, or null if it is SQL null; never returns the empty string
*/
String getClobStringOrNull();
/**
* @return the value, or null if it is SQL null; never returns the empty string
*/
String getClobStringOrNull(int columnOneBased);
/**
* @return the value, or null if it is SQL null; never returns the empty string
*/
String getClobStringOrNull(String columnName);
/**
* @return the value, or the empty string if it is SQL null; never returns null
*/
String getClobStringOrEmpty();
/**
* @return the value, or the empty string if it is SQL null; never returns null
*/
String getClobStringOrEmpty(int columnOneBased);
/**
* @return the value, or the empty string if it is SQL null; never returns null
*/
String getClobStringOrEmpty(String columnName);
/**
* @return the value, or null if it is SQL null
*/
Reader getClobReaderOrNull();
/**
* @return the value, or null if it is SQL null
*/
Reader getClobReaderOrNull(int columnOneBased);
/**
* @return the value, or null if it is SQL null
*/
Reader getClobReaderOrNull(String columnName);
/**
* @return the value, or a StringReader containing the empty string if it is SQL null
*/
Reader getClobReaderOrEmpty();
/**
* @return the value, or a StringReader containing the empty string if it is SQL null
*/
Reader getClobReaderOrEmpty(int columnOneBased);
/**
* @return the value, or a StringReader containing the empty string if it is SQL null
*/
Reader getClobReaderOrEmpty(String columnName);
byte[] getBlobBytesOrNull();
byte[] getBlobBytesOrNull(int columnOneBased);
byte[] getBlobBytesOrNull(String columnName);
byte[] getBlobBytesOrZeroLen();
byte[] getBlobBytesOrZeroLen(int columnOneBased);
byte[] getBlobBytesOrZeroLen(String columnName);
InputStream getBlobInputStreamOrNull();
InputStream getBlobInputStreamOrNull(int columnOneBased);
InputStream getBlobInputStreamOrNull(String columnName);
InputStream getBlobInputStreamOrEmpty();
InputStream getBlobInputStreamOrEmpty(int columnOneBased);
InputStream getBlobInputStreamOrEmpty(String columnName);
/**
* Return the millisecond precision Date, which should be represented as a TIMESTAMP
* in the database. The nanoseconds are truncated.
*/
Date getDateOrNull();
/**
* Return the millisecond precision Date, which should be represented as a TIMESTAMP
* in the database. The nanoseconds are truncated.
*/
Date getDateOrNull(int columnOneBased);
Date getDateOrNull(String columnName);
/**
* Retrieve column as LocalDate, .i.e, date with no time.
*
* @return LocalDate of the database column value
*/
LocalDate getLocalDateOrNull();
/**
* Get the Date field, with no timestamp
*
* @param columnOneBased column number starting at 1, not 0
* @return LocalDate of the column value
*/
LocalDate getLocalDateOrNull(int columnOneBased);
/**
* Get the Date field, with no timestamp
*
* @param columnName column name to retrieve
* @return LocalDate of the column value
*/
LocalDate getLocalDateOrNull(String columnName);
}

@ -0,0 +1,8 @@
package org.xbib.jdbc.query;
/**
* Type-safe callback to read query results.
*/
public interface RowHandler<T> {
T process(Row r) throws Exception;
}

@ -0,0 +1,8 @@
package org.xbib.jdbc.query;
/**
* Interface for reading results from a database query.
*/
public interface Rows extends Row {
boolean next();
}

@ -0,0 +1,835 @@
package org.xbib.jdbc.query;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.util.Date;
/**
* Safely wrap a ResultSet and provide access to the data it contains.
*/
class RowsAdaptor implements Rows {
private final ResultSet rs;
private final Options options;
private int column = 1;
public RowsAdaptor(ResultSet rs, Options options) {
this.rs = rs;
this.options = options;
}
@Override
public boolean next() {
try {
column = 1;
return rs.next();
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public String[] getColumnLabels() {
try {
ResultSetMetaData metaData = rs.getMetaData();
String[] names = new String[metaData.getColumnCount()];
for (int i = 0; i < names.length; i++) {
names[i] = metaData.getColumnLabel(i + 1);
}
return names;
} catch (SQLException e) {
throw new DatabaseException("Unable to retrieve metadata from ResultSet", e);
}
}
@Override
public ResultSetMetaData getMetadata() {
try {
return rs.getMetaData();
} catch (SQLException e) {
throw new DatabaseException("Unable to retrieve metadata from ResultSet", e);
}
}
@Override
public Boolean getBooleanOrNull() {
return getBooleanOrNull(column++);
}
@Override
public Boolean getBooleanOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
return toBoolean(rs, columnOneBased);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public Boolean getBooleanOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
return toBoolean(rs, columnName);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public boolean getBooleanOrFalse() {
return getBooleanOrFalse(column++);
}
@Override
public boolean getBooleanOrFalse(int columnOneBased) {
Boolean result = getBooleanOrNull(columnOneBased);
if (result == null) {
result = Boolean.FALSE;
}
return result;
}
@Override
public boolean getBooleanOrFalse(String columnName) {
Boolean result = getBooleanOrNull(columnName);
if (result == null) {
result = Boolean.FALSE;
}
return result;
}
@Override
public boolean getBooleanOrTrue() {
return getBooleanOrTrue(column++);
}
@Override
public boolean getBooleanOrTrue(int columnOneBased) {
Boolean result = getBooleanOrNull(columnOneBased);
if (result == null) {
result = Boolean.TRUE;
}
return result;
}
@Override
public boolean getBooleanOrTrue(String columnName) {
Boolean result = getBooleanOrNull(columnName);
if (result == null) {
result = Boolean.TRUE;
}
return result;
}
@Override
public Integer getIntegerOrNull() {
return getIntegerOrNull(column++);
}
@Override
public Integer getIntegerOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
return toInteger(rs, columnOneBased);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public Integer getIntegerOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
return toInteger(rs, columnName);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public int getIntegerOrZero() {
return getIntegerOrZero(column++);
}
@Override
public int getIntegerOrZero(int columnOneBased) {
Integer result = getIntegerOrNull(columnOneBased);
if (result == null) {
result = 0;
}
return result;
}
@Override
public int getIntegerOrZero(String columnName) {
Integer result = getIntegerOrNull(columnName);
if (result == null) {
result = 0;
}
return result;
}
@Override
public Long getLongOrNull() {
return getLongOrNull(column++);
}
@Override
public Long getLongOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
return toLong(rs, columnOneBased);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public Long getLongOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
return toLong(rs, columnName);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public long getLongOrZero() {
return getLongOrZero(column++);
}
@Override
public long getLongOrZero(int columnOneBased) {
Long result = getLongOrNull(columnOneBased);
if (result == null) {
result = 0L;
}
return result;
}
@Override
public long getLongOrZero(String columnName) {
Long result = getLongOrNull(columnName);
if (result == null) {
result = 0L;
}
return result;
}
@Override
public Float getFloatOrNull() {
return getFloatOrNull(column++);
}
@Override
public Float getFloatOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
return toFloat(rs, columnOneBased);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public Float getFloatOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
return toFloat(rs, columnName);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public float getFloatOrZero() {
return getFloatOrZero(column++);
}
@Override
public float getFloatOrZero(int columnOneBased) {
Float result = getFloatOrNull(columnOneBased);
if (result == null) {
result = 0f;
}
return result;
}
@Override
public float getFloatOrZero(String columnName) {
Float result = getFloatOrNull(columnName);
if (result == null) {
result = 0f;
}
return result;
}
@Override
public Double getDoubleOrNull() {
return getDoubleOrNull(column++);
}
@Override
public Double getDoubleOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
return toDouble(rs, columnOneBased);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public Double getDoubleOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
return toDouble(rs, columnName);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public double getDoubleOrZero() {
return getDoubleOrZero(column++);
}
@Override
public double getDoubleOrZero(int columnOneBased) {
Double result = getDoubleOrNull(columnOneBased);
if (result == null) {
result = 0d;
}
return result;
}
@Override
public double getDoubleOrZero(String columnName) {
Double result = getDoubleOrNull(columnName);
if (result == null) {
result = 0d;
}
return result;
}
@Override
public BigDecimal getBigDecimalOrNull() {
return getBigDecimalOrNull(column++);
}
@Override
public BigDecimal getBigDecimalOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
return toBigDecimal(rs, columnOneBased);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public BigDecimal getBigDecimalOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
return toBigDecimal(rs, columnName);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public BigDecimal getBigDecimalOrZero() {
return getBigDecimalOrZero(column++);
}
@Override
public BigDecimal getBigDecimalOrZero(int columnOneBased) {
BigDecimal result = getBigDecimalOrNull(columnOneBased);
if (result == null) {
result = BigDecimal.ZERO;
}
return result;
}
@Override
public BigDecimal getBigDecimalOrZero(String columnName) {
BigDecimal result = getBigDecimalOrNull(columnName);
if (result == null) {
result = BigDecimal.ZERO;
}
return result;
}
@Override
public String getStringOrNull() {
return getStringOrNull(column++);
}
@Override
public String getStringOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
String result = rs.getString(columnOneBased);
if (result != null && result.length() == 0) {
result = null;
}
return result;
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public String getStringOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
String result = rs.getString(columnName);
if (result != null && result.length() == 0) {
result = null;
}
return result;
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public String getStringOrEmpty() {
return getStringOrEmpty(column++);
}
@Override
public String getStringOrEmpty(int columnOneBased) {
String result = getStringOrNull(columnOneBased);
if (result == null) {
result = "";
}
return result;
}
@Override
public String getStringOrEmpty(String columnName) {
String result = getStringOrNull(columnName);
if (result == null) {
result = "";
}
return result;
}
@Override
public String getClobStringOrNull() {
return getClobStringOrNull(column++);
}
@Override
public String getClobStringOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
String result = rs.getString(columnOneBased);
if (result != null && result.length() == 0) {
result = null;
}
return result;
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public String getClobStringOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
String result = rs.getString(columnName);
if (result != null && result.length() == 0) {
result = null;
}
return result;
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public String getClobStringOrEmpty() {
return getClobStringOrEmpty(column++);
}
@Override
public String getClobStringOrEmpty(int columnOneBased) {
String result = getClobStringOrNull(columnOneBased);
if (result == null) {
result = "";
}
return result;
}
@Override
public String getClobStringOrEmpty(String columnName) {
String result = getClobStringOrNull(columnName);
if (result == null) {
result = "";
}
return result;
}
@Override
public Reader getClobReaderOrNull() {
return getClobReaderOrNull(column++);
}
@Override
public Reader getClobReaderOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
return rs.getCharacterStream(columnOneBased);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public Reader getClobReaderOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
return rs.getCharacterStream(columnName);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public Reader getClobReaderOrEmpty() {
return getClobReaderOrEmpty(column++);
}
@Override
public Reader getClobReaderOrEmpty(int columnOneBased) {
Reader result = getClobReaderOrNull(columnOneBased);
if (result == null) {
result = new StringReader("");
}
return result;
}
@Override
public Reader getClobReaderOrEmpty(String columnName) {
Reader result = getClobReaderOrNull(columnName);
if (result == null) {
result = new StringReader("");
}
return result;
}
@Override
public byte[] getBlobBytesOrNull() {
return getBlobBytesOrNull(column++);
}
@Override
public byte[] getBlobBytesOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
return rs.getBytes(columnOneBased);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public byte[] getBlobBytesOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
return rs.getBytes(columnName);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public byte[] getBlobBytesOrZeroLen() {
return getBlobBytesOrZeroLen(column++);
}
@Override
public byte[] getBlobBytesOrZeroLen(int columnOneBased) {
byte[] result = getBlobBytesOrNull(columnOneBased);
if (result == null) {
result = new byte[0];
}
return result;
}
@Override
public byte[] getBlobBytesOrZeroLen(String columnName) {
byte[] result = getBlobBytesOrNull(columnName);
if (result == null) {
result = new byte[0];
}
return result;
}
@Override
public InputStream getBlobInputStreamOrNull() {
return getBlobInputStreamOrNull(column++);
}
@Override
public InputStream getBlobInputStreamOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
return rs.getBinaryStream(columnOneBased);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public InputStream getBlobInputStreamOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
return rs.getBinaryStream(columnName);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public InputStream getBlobInputStreamOrEmpty() {
return getBlobInputStreamOrEmpty(column++);
}
@Override
public InputStream getBlobInputStreamOrEmpty(int columnOneBased) {
InputStream result = getBlobInputStreamOrNull(columnOneBased);
if (result == null) {
result = new ByteArrayInputStream(new byte[0]);
}
return result;
}
@Override
public InputStream getBlobInputStreamOrEmpty(String columnName) {
InputStream result = getBlobInputStreamOrNull(columnName);
if (result == null) {
result = new ByteArrayInputStream(new byte[0]);
}
return result;
}
@Override
public Date getDateOrNull() {
return getDateOrNull(column++);
}
@Override
public Date getDateOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
return toDate(rs, columnOneBased);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public Date getDateOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
return toDate(rs, columnName);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public LocalDate getLocalDateOrNull() {
return getLocalDateOrNull(column++);
}
@Override
public LocalDate getLocalDateOrNull(int columnOneBased) {
try {
column = columnOneBased + 1;
return toLocalDate(rs, columnOneBased);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public LocalDate getLocalDateOrNull(String columnName) {
try {
column = rs.findColumn(columnName) + 1;
return toLocalDate(rs, columnName);
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
/**
* Make sure the Timestamp will return getTime() accurate to the millisecond
* (if possible) and truncate away nanoseconds.
*/
private Date timestampToDate(Timestamp ts) {
long millis = ts.getTime();
int nanos = ts.getNanos();
return new Date(millis / 1000 * 1000 + nanos / 1000000);
}
private Date toDate(ResultSet rs, int col) throws SQLException {
Timestamp val = rs.getTimestamp(col, options.calendarForTimestamps());
return val == null ? null : timestampToDate(val);
}
private Date toDate(ResultSet rs, String col) throws SQLException {
Timestamp val = rs.getTimestamp(col, options.calendarForTimestamps());
return val == null ? null : timestampToDate(val);
}
private LocalDate toLocalDate(ResultSet rs, int col) throws SQLException {
java.sql.Date val = rs.getDate(col);
return val == null ? null : val.toLocalDate();
}
private LocalDate toLocalDate(ResultSet rs, String col) throws SQLException {
java.sql.Date val = rs.getDate(col);
return val == null ? null : val.toLocalDate();
}
private Boolean toBoolean(ResultSet rs, int col) throws SQLException {
String val = rs.getString(col);
if (val == null) {
return null;
} else if (val.equals("Y") || val.equals("1")) {
return Boolean.TRUE;
} else if (val.equals("N") || val.equals("0")) {
return Boolean.FALSE;
} else {
throw new DatabaseException("Reading boolean from column " + col + " but the value was not 'Y' or 'N'");
}
}
private Boolean toBoolean(ResultSet rs, String col) throws SQLException {
String val = rs.getString(col);
if (val == null) {
return null;
} else if (val.equals("Y") || val.equals("1")) {
return Boolean.TRUE;
} else if (val.equals("N") || val.equals("0")) {
return Boolean.FALSE;
} else {
throw new DatabaseException("Reading boolean from column \"" + col + "\" but the value was not 'Y' or 'N'");
}
}
private Integer toInteger(ResultSet rs, int col) throws SQLException {
int val = rs.getInt(col);
return rs.wasNull() ? null : val;
}
private Integer toInteger(ResultSet rs, String col) throws SQLException {
int val = rs.getInt(col);
return rs.wasNull() ? null : val;
}
private Long toLong(ResultSet rs, int col) throws SQLException {
long val = rs.getLong(col);
return rs.wasNull() ? null : val;
}
private Long toLong(ResultSet rs, String col) throws SQLException {
long val = rs.getLong(col);
return rs.wasNull() ? null : val;
}
private Float toFloat(ResultSet rs, int col) throws SQLException {
float val = rs.getFloat(col);
return rs.wasNull() ? null : val;
}
private Float toFloat(ResultSet rs, String col) throws SQLException {
float val = rs.getFloat(col);
return rs.wasNull() ? null : val;
}
private Double toDouble(ResultSet rs, int col) throws SQLException {
double val = rs.getDouble(col);
return rs.wasNull() ? null : val;
}
private Double toDouble(ResultSet rs, String col) throws SQLException {
double val = rs.getDouble(col);
return rs.wasNull() ? null : val;
}
private BigDecimal fixBigDecimal(BigDecimal val) {
if (val.scale() > 0) {
val = val.stripTrailingZeros();
if (val.scale() < 0) {
val = val.setScale(0);
}
}
return val;
}
private BigDecimal toBigDecimal(ResultSet rs, int col) throws SQLException {
BigDecimal val = rs.getBigDecimal(col);
return val == null ? null : fixBigDecimal(val);
}
private BigDecimal toBigDecimal(ResultSet rs, String col) throws SQLException {
BigDecimal val = rs.getBigDecimal(col);
return val == null ? null : fixBigDecimal(val);
}
}

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

Loading…
Cancel
Save