diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..c32394f1 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or 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 + * + * 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. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.5"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar old mode 100755 new mode 100644 index f775b1c0..0d5e6498 Binary files a/.mvn/wrapper/maven-wrapper.jar and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties old mode 100755 new mode 100644 index a447c9fa..fa87ad7d --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1 +1,2 @@ -distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip \ No newline at end of file +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.1/apache-maven-3.6.1-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar diff --git a/README.md b/README.md index ca9205cc..f790ed3b 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,11 @@ To get the Java connector for Tarantool 1.6.9, visit ## Table of contents * [Getting started](#getting-started) +* [Spring NamedParameterJdbcTemplate usage example](#spring-namedparameterjdbctemplate-usage-example) * [JDBC](#JDBC) * [Cluster support](#cluster-support) +* [Logging](#logging) +* [Building](#building) * [Where to get help](#where-to-get-help) ## Getting started @@ -395,7 +398,7 @@ client.syncOps().insert(45, Arrays.asList(1, 1)); The client does its best to catch the moment when there are no pending responses and perform a reconnection. -### Client config options +### Cluster client config options In addition to the options for [the standard client](#client-config-options), cluster config provides some extra options: @@ -409,12 +412,41 @@ config provides some extra options: cluster nodes. Default value is `60 * 1000` (1 minute). -## Where to get help +## Logging -Got problems or questions? Post them on -[Stack Overflow](http://stackoverflow.com/questions/ask/advice) with the -`tarantool` and `java` tags, or use these tags to search the existing knowledge -base for possible answers and solutions. +The connector uses its own logging facade to abstract from any logging libraries +which can be used inside the apps where the connector attached. At the moment, +the facade supports JUL as a default logging system, SLF4J facade, and Logback +directly via SLF4J interface. + +### Logging integration + +The logging facade offers several ways in integrate its internal logging with foreign one in order: + +* Using system property `org.tarantool.logging.provider`. Supported values are *jdk* and *slf4j* + for the java util logging and SLF4J/Logback respectively. For instance, use + `java -Dorg.tarantool.logging.provider=slf4j <...>`. + +* Using Java SPI mechanism. Implement your own provider org.tarantool.logging.LoggerProvider + To register your provider save `META-INF.services/org.tarantool.logging.LoggerProvider` file + with a single line text contains a fully-qualified class name of the provider implementation. + +```bash +cat META-INF/services/org.tarantool.logging.LoggerProvider +org.mydomain.MySimpleLoggerProvider +``` + +* CLASSPATH exploring. Now, the connector will use SLF4J if Logback is also in use. + +* If nothing is successful JUL will be used by default. + +### Supported loggers + +| Logger name | Level | Description | +| ---------------------------------------------- | ----- | ------------------------------------------------- | +| o.t.c.TarantoolClusterStoredFunctionDiscoverer | WARN | prints out invalid discovery addresses | +| o.t.TarantoolClusterClient | TRACE | prints out request retries after transient errors | +| o.t.TarantoolClientImpl | WARN | prints out reconnect issues | ## Building @@ -429,3 +461,10 @@ To run integration tests use: ```bash ./mvnw clean verify ``` + +## Where to get help + +Got problems or questions? Post them on +[Stack Overflow](http://stackoverflow.com/questions/ask/advice) with the +`tarantool` and `java` tags, or use these tags to search the existing knowledge +base for possible answers and solutions. diff --git a/mvnw b/mvnw index e96ccd5f..d2f0ea38 100755 --- a/mvnw +++ b/mvnw @@ -114,7 +114,6 @@ if $mingw ; then M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? fi if [ -z "$JAVA_HOME" ]; then @@ -200,6 +199,85 @@ if [ -z "$BASE_DIR" ]; then exit 1; fi +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} if [ "$MVNW_VERBOSE" = true ]; then echo $MAVEN_PROJECTBASEDIR @@ -218,6 +296,11 @@ if $cygwin; then MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ diff --git a/mvnw.cmd b/mvnw.cmd old mode 100755 new mode 100644 index 4f0b068a..b26ab24f --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,145 +1,182 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" - -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index f7a19e30..f2a438fd 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 org.tarantool @@ -10,6 +11,9 @@ 5.4.2 1.23 1.10.19 + + 1.7.26 + ${project.basedir}/src/test/resources Tarantool Connector for Java https://github.com/tarantool/tarantool-java @@ -71,17 +75,24 @@ templating-maven-plugin 1.0.0 - - filter-src - - filter-sources - - + + filter-src + + filter-sources + + maven-surefire-plugin 3.0.0-M3 + + + + ${project.testResources}/jul-silent.properties + + + org.apache.maven.plugins @@ -97,6 +108,12 @@ false + + + ${project.testResources}/jul-silent.properties + + + @@ -189,6 +206,12 @@ ${snakeyml.version} test + + org.slf4j + slf4j-api + ${sfl4j.version} + provided + diff --git a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java index 804c869b..5817665e 100644 --- a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java +++ b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java @@ -13,6 +13,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; import java.util.stream.Collectors; /** @@ -93,13 +94,7 @@ private void updateAddressList(Collection addresses) { * @return socket addresses */ public List getAddresses() { - Lock readLock = addressListLock.readLock(); - readLock.lock(); - try { - return Collections.unmodifiableList(this.socketAddresses); - } finally { - readLock.unlock(); - } + return readGuard(() -> Collections.unmodifiableList(this.socketAddresses)); } /** @@ -109,14 +104,10 @@ public List getAddresses() { * if {@link #currentPosition} has {@link #UNSET_POSITION} value */ protected InetSocketAddress getLastObtainedAddress() { - Lock readLock = addressListLock.readLock(); - readLock.lock(); - try { + return readGuard(() -> { int index = currentPosition.get(); return index != UNSET_POSITION ? socketAddresses.get(index) : null; - } finally { - readLock.unlock(); - } + }); } /** @@ -176,13 +167,7 @@ public void setRetriesLimit(int retriesLimit) { * @return Number of configured addresses. */ protected int getAddressCount() { - Lock readLock = addressListLock.readLock(); - readLock.lock(); - try { - return socketAddresses.size(); - } finally { - readLock.unlock(); - } + return readGuard(socketAddresses::size); } /** @@ -191,11 +176,25 @@ protected int getAddressCount() { * @return Socket address to use for the next reconnection attempt */ protected InetSocketAddress getNextSocketAddress() { + return readGuard(() -> { + int position = currentPosition.updateAndGet(i -> (i + 1) % socketAddresses.size()); + return socketAddresses.get(position); + }); + } + + @Override + public SocketAddress getAddress() { + return readGuard(() -> { + int position = (currentPosition.get() + 1) % socketAddresses.size(); + return socketAddresses.get(position); + }); + } + + private R readGuard(Supplier supplier) { Lock readLock = addressListLock.readLock(); readLock.lock(); try { - int position = currentPosition.updateAndGet(i -> (i + 1) % socketAddresses.size()); - return socketAddresses.get(position); + return supplier.get(); } finally { readLock.unlock(); } diff --git a/src/main/java/org/tarantool/SingleSocketChannelProviderImpl.java b/src/main/java/org/tarantool/SingleSocketChannelProviderImpl.java index ef1fc59b..2a7c1cb7 100644 --- a/src/main/java/org/tarantool/SingleSocketChannelProviderImpl.java +++ b/src/main/java/org/tarantool/SingleSocketChannelProviderImpl.java @@ -24,6 +24,7 @@ public SingleSocketChannelProviderImpl(String address) { setAddress(address); } + @Override public SocketAddress getAddress() { return address; } diff --git a/src/main/java/org/tarantool/SocketChannelProvider.java b/src/main/java/org/tarantool/SocketChannelProvider.java index a811bb1f..c724b7de 100644 --- a/src/main/java/org/tarantool/SocketChannelProvider.java +++ b/src/main/java/org/tarantool/SocketChannelProvider.java @@ -19,4 +19,15 @@ public interface SocketChannelProvider { */ SocketChannel get(int retryNumber, Throwable lastError); + /** + * Gets an address that will be used when + * {@link #get(int, Throwable)} is invoked. + * + * @return effective address or {@literal null} + * if it cannot be calculated in advance. + */ + default SocketAddress getAddress() { + return null; + } + } diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index bad3158c..85e8680b 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -1,11 +1,15 @@ package org.tarantool; +import org.tarantool.logging.Logger; +import org.tarantool.logging.LoggerFactory; import org.tarantool.protocol.ProtoUtils; import org.tarantool.protocol.ReadableViaSelectorChannel; import org.tarantool.protocol.TarantoolGreeting; import org.tarantool.protocol.TarantoolPacket; +import org.tarantool.util.StringUtils; import java.io.IOException; +import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.Iterator; @@ -28,6 +32,8 @@ public class TarantoolClientImpl extends TarantoolBase> implements TarantoolClient { + private static final Logger LOGGER = LoggerFactory.getLogger(TarantoolClientImpl.class); + protected TarantoolClientConfig config; protected long operationTimeout; @@ -143,6 +149,15 @@ protected void reconnect(Throwable lastError) { int retryNumber = 0; while (!Thread.currentThread().isInterrupted()) { try { + if (lastError != null) { + LOGGER.warn(() -> { + SocketAddress address = socketProvider.getAddress(); + return "Attempt to (re-)connect to Tarantool instance " + + (StringUtils.isBlank(config.username) ? "" : config.username + ":*****@") + + (address == null ? "unknown" : address); + }, lastError + ); + } channel = socketProvider.get(retryNumber++, lastError); } catch (Exception e) { closeChannel(channel); @@ -710,6 +725,14 @@ public Object[] getArgs() { return args; } + @Override + public String toString() { + return "TarantoolOp{" + + "id=" + id + + ", code=" + code + + '}'; + } + /** * Missed in jdk8 CompletableFuture operator to limit execution * by time. diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index b0c4711b..e3697a91 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -2,6 +2,8 @@ import org.tarantool.cluster.TarantoolClusterDiscoverer; import org.tarantool.cluster.TarantoolClusterStoredFunctionDiscoverer; +import org.tarantool.logging.Logger; +import org.tarantool.logging.LoggerFactory; import org.tarantool.protocol.TarantoolPacket; import org.tarantool.util.StringUtils; @@ -27,6 +29,8 @@ */ public class TarantoolClusterClient extends TarantoolClientImpl { + private static final Logger LOGGER = LoggerFactory.getLogger(TarantoolClusterClient.class); + /** * Need some execution context to retry writes. */ @@ -153,6 +157,7 @@ protected boolean checkFail(TarantoolOp future, Exception e) { } else { assert retries != null; retries.put(future.getId(), future); + LOGGER.trace("Request {0} was delayed because of {1}", future, e); return false; } } @@ -194,13 +199,18 @@ protected void onReconnect() { // First call is before the constructor finished. Skip it. return; } - Collection> futuresToRetry = new ArrayList<>(retries.values()); + Collection> delayed = new ArrayList<>(retries.values()); + Collection> reissued = new ArrayList<>(retries.size()); retries.clear(); - for (final TarantoolOp future : futuresToRetry) { + for (final TarantoolOp future : delayed) { if (!future.isDone()) { executor.execute(() -> registerOperation(future)); + reissued.add(future); } } + for (final TarantoolOp future : reissued) { + LOGGER.trace("{0} was re-issued after reconnection", future); + } } @Override diff --git a/src/main/java/org/tarantool/cluster/TarantoolClusterStoredFunctionDiscoverer.java b/src/main/java/org/tarantool/cluster/TarantoolClusterStoredFunctionDiscoverer.java index da92cd3a..3b79819e 100644 --- a/src/main/java/org/tarantool/cluster/TarantoolClusterStoredFunctionDiscoverer.java +++ b/src/main/java/org/tarantool/cluster/TarantoolClusterStoredFunctionDiscoverer.java @@ -3,12 +3,13 @@ import org.tarantool.TarantoolClient; import org.tarantool.TarantoolClientOps; import org.tarantool.TarantoolClusterClientConfig; +import org.tarantool.logging.Logger; +import org.tarantool.logging.LoggerFactory; import org.tarantool.util.StringUtils; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; /** * A cluster nodes discoverer based on calling a predefined function @@ -19,6 +20,8 @@ */ public class TarantoolClusterStoredFunctionDiscoverer implements TarantoolClusterDiscoverer { + private static final Logger LOGGER = LoggerFactory.getLogger(TarantoolClusterStoredFunctionDiscoverer.class); + private TarantoolClient client; private String entryFunction; @@ -57,12 +60,24 @@ private Set checkAndFilterAddresses(List result) { throw new IllegalDiscoveryFunctionResult("The first value must be an array of strings"); } - return ((List) result.get(0)).stream() - .filter(item -> item instanceof String) - .map(Object::toString) - .filter(s -> !StringUtils.isBlank(s)) - .filter(this::isAddress) - .collect(Collectors.toCollection(LinkedHashSet::new)); + LinkedHashSet passed = new LinkedHashSet<>(); + LinkedHashSet skipped = new LinkedHashSet<>(); + for (Object item : ((List) result.get(0))) { + if (!(item instanceof String)) { + skipped.add(item.toString()); + continue; + } + String s = item.toString(); + if (!StringUtils.isBlank(s) && isAddress(s)) { + passed.add(s); + } else { + skipped.add(s); + } + } + if (!skipped.isEmpty()) { + LOGGER.warn("Some of the addresses were skipped because of unsupported format: {0}", skipped); + } + return passed; } /** diff --git a/src/main/java/org/tarantool/logging/BaseLogger.java b/src/main/java/org/tarantool/logging/BaseLogger.java new file mode 100644 index 00000000..8a108f88 --- /dev/null +++ b/src/main/java/org/tarantool/logging/BaseLogger.java @@ -0,0 +1,143 @@ +package org.tarantool.logging; + +import java.util.function.Supplier; + +abstract class BaseLogger implements Logger { + + public abstract void log(Level level, String text, Throwable error, Object... parameters); + + public void log(Level level, Throwable error, Supplier message) { + if (!isLoggable(level)) { + return; + } + log(level, message.get(), error); + } + + public abstract boolean isLoggable(Level level); + + @Override + public void debug(String message) { + log(Level.DEBUG, message, null); + } + + @Override + public void debug(String format, Object... params) { + log(Level.DEBUG, format, null, params); + } + + @Override + public void debug(String message, Throwable throwable) { + log(Level.DEBUG, message, throwable); + } + + @Override + public void debug(Supplier message, Throwable throwable) { + log(Level.DEBUG, throwable, message); + } + + @Override + public void error(String message) { + log(Level.ERROR, message, null); + } + + @Override + public void error(String format, Object... params) { + log(Level.ERROR, format, null, params); + } + + @Override + public void error(String message, Throwable throwable) { + log(Level.ERROR, message, throwable); + } + + @Override + public void error(Supplier message, Throwable throwable) { + log(Level.ERROR, throwable, message); + } + + @Override + public void info(String message) { + log(Level.INFO, message, null); + } + + @Override + public void info(String format, Object... params) { + log(Level.INFO, format, null, params); + } + + @Override + public void info(String message, Throwable throwable) { + log(Level.INFO, message, throwable); + } + + @Override + public void info(Supplier message, Throwable throwable) { + log(Level.INFO, throwable, message); + } + + @Override + public void trace(String message) { + log(Level.TRACE, message, null); + } + + @Override + public void trace(String format, Object... params) { + log(Level.TRACE, format, null, params); + } + + @Override + public void trace(String message, Throwable throwable) { + log(Level.TRACE, message, throwable); + } + + @Override + public void trace(Supplier message, Throwable throwable) { + log(Level.TRACE, throwable, message); + } + + @Override + public void warn(String message) { + log(Level.WARN, message, null); + } + + @Override + public void warn(String format, Object... params) { + log(Level.WARN, format, null, params); + } + + @Override + public void warn(String message, Throwable throwable) { + log(Level.WARN, message, throwable); + } + + @Override + public void warn(Supplier message, Throwable throwable) { + log(Level.WARN, throwable, message); + } + + @Override + public boolean isDebugEnabled() { + return isLoggable(Level.DEBUG); + } + + @Override + public boolean isErrorEnabled() { + return isLoggable(Level.ERROR); + } + + @Override + public boolean isInfoEnabled() { + return isLoggable(Level.INFO); + } + + @Override + public boolean isTraceEnabled() { + return isLoggable(Level.TRACE); + } + + @Override + public boolean isWarnEnabled() { + return isLoggable(Level.WARN); + } + +} diff --git a/src/main/java/org/tarantool/logging/JdkLogger.java b/src/main/java/org/tarantool/logging/JdkLogger.java new file mode 100644 index 00000000..dea8b897 --- /dev/null +++ b/src/main/java/org/tarantool/logging/JdkLogger.java @@ -0,0 +1,65 @@ +package org.tarantool.logging; + +import java.util.logging.LogRecord; + +/** + * Adapter to JUL logger backend. + *

+ * This class is not a part of public API. + * + * @see java.util.logging + */ +class JdkLogger extends BaseLogger { + + private final java.util.logging.Logger delegate; + + public JdkLogger(String loggerName) { + this.delegate = java.util.logging.Logger.getLogger(loggerName); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public void log(Level level, String text, Throwable error, Object... parameters) { + if (!isLoggable(level)) { + return; + } + + java.util.logging.Level julLevel = translate(level); + LogRecord record = new LogRecord(julLevel, text); + record.setLoggerName(delegate.getName()); + record.setResourceBundle(delegate.getResourceBundle()); + record.setResourceBundleName(delegate.getResourceBundleName()); + if (error != null) { + record.setThrown(error); + } + record.setParameters(parameters); + delegate.log(record); + } + + @Override + public boolean isLoggable(Level level) { + return delegate.isLoggable(translate(level)); + } + + private java.util.logging.Level translate(Level level) { + switch (level) { + case ERROR: + return java.util.logging.Level.SEVERE; + case WARN: + return java.util.logging.Level.WARNING; + case INFO: + return java.util.logging.Level.INFO; + case DEBUG: + return java.util.logging.Level.FINE; + case TRACE: + return java.util.logging.Level.FINEST; + default: + return java.util.logging.Level.ALL; + } + } + +} diff --git a/src/main/java/org/tarantool/logging/JdkLoggerProvider.java b/src/main/java/org/tarantool/logging/JdkLoggerProvider.java new file mode 100644 index 00000000..b6cca9fc --- /dev/null +++ b/src/main/java/org/tarantool/logging/JdkLoggerProvider.java @@ -0,0 +1,15 @@ +package org.tarantool.logging; + +/** + * Delegates a logger creation to JUL logger adapter. + *

+ * This class is not a part of public API. + */ +public class JdkLoggerProvider implements LoggerProvider { + + @Override + public Logger getLogger(String name) { + return new JdkLogger(name); + } + +} diff --git a/src/main/java/org/tarantool/logging/Logger.java b/src/main/java/org/tarantool/logging/Logger.java new file mode 100644 index 00000000..37524b4d --- /dev/null +++ b/src/main/java/org/tarantool/logging/Logger.java @@ -0,0 +1,73 @@ +package org.tarantool.logging; + +import java.util.function.Supplier; + +/** + * Minimal logger contract to perform internal + * logging. + *

+ * This class is not a part of public API. + */ +public interface Logger { + + enum Level { + ERROR, + WARN, + INFO, + DEBUG, + TRACE + } + + String getName(); + + boolean isErrorEnabled(); + + void error(final String message); + + void error(final String format, final Object... params); + + void error(final String message, Throwable throwable); + + void error(final Supplier message, Throwable throwable); + + boolean isWarnEnabled(); + + void warn(final String message); + + void warn(final String format, final Object... params); + + void warn(final String message, Throwable throwable); + + void warn(final Supplier message, Throwable throwable); + + boolean isInfoEnabled(); + + void info(final String message); + + void info(final String format, final Object... params); + + void info(final String message, Throwable throwable); + + void info(final Supplier message, Throwable throwable); + + boolean isDebugEnabled(); + + void debug(final String message); + + void debug(final String format, final Object... params); + + void debug(final String message, Throwable throwable); + + void debug(final Supplier message, Throwable throwable); + + boolean isTraceEnabled(); + + void trace(final String message); + + void trace(final String format, final Object... params); + + void trace(final String message, Throwable throwable); + + void trace(final Supplier message, Throwable throwable); + +} diff --git a/src/main/java/org/tarantool/logging/LoggerFactory.java b/src/main/java/org/tarantool/logging/LoggerFactory.java new file mode 100644 index 00000000..9a2227bc --- /dev/null +++ b/src/main/java/org/tarantool/logging/LoggerFactory.java @@ -0,0 +1,122 @@ +package org.tarantool.logging; + +import java.util.Iterator; +import java.util.ServiceLoader; + +/** + * Basic service to load appropriate {@link LoggerProvider} + * and obtain new loggers using loaded provider. + *

+ * This class should be used as a start point to obtain the logger: + *

 {@code
+ * static final Logger LOGGER = LoggerFactory.getLogger(MyClass.java);
+ * } 
+ * and then it can be used as a standard logger: + *
 {@code
+ * LOGGER.info("Service initialized");
+ * } 
+ *

+ * There are four major attempts to load the logger provider: + *

  • + * Use a runtime system property {@literal org.tarantool.logging.provider}. + * Possible values are 'slf4j' or 'jdk'. (for instance, + * {@literal -Dorg.tarantool.logging.provider=slf4j}). + *
  • + *
  • + * Use SPI mechanism to find the proper {@link LoggerProvider}. + *
  • + *
  • + * Check the classpath in attempt to discover one of supported logger + * backends. + *
  • + *
  • + * Use JUL implementation if none of above attempts are successful. + *
  • + *

    + * This class is not a part of public API. + */ +public class LoggerFactory { + + private static final String LOGGING_PROVIDER_KEY = "org.tarantool.logging.provider"; + private static final String LOGGING_PROVIDER_JDK = "jdk"; + private static final String LOGGING_PROVIDER_SLF4J = "slf4j"; + + private static LoggerProvider loggerProvider = loadLoggerProvider(); + + private static LoggerProvider loadLoggerProvider() { + // use a runtime property to determine a logger provider name + try { + String providerName = System.getProperty(LOGGING_PROVIDER_KEY); + if (providerName != null) { + if (providerName.equalsIgnoreCase(LOGGING_PROVIDER_JDK)) { + return tryLoadJdkProvider(); + } else if (providerName.equalsIgnoreCase(LOGGING_PROVIDER_SLF4J)) { + return tryLoadSlf4jProvider(); + } + } + } catch (Throwable ignored) { + // no-op + } + + // use SPI to pick a proper logger provider + try { + ServiceLoader serviceLoader = ServiceLoader.load(LoggerProvider.class); + Iterator iterator = serviceLoader.iterator(); + while (loggerProvider == null && iterator.hasNext()) { + try { + return iterator.next(); + } catch (Throwable ignored) { + // no-op + } + } + } catch (Throwable ignored) { + // no-op + } + + // check slf4j-logback existence in the class path + try { + Class.forName("ch.qos.logback.classic.Logger", false, LoggerFactory.class.getClassLoader()); + return tryLoadSlf4jProvider(); + } catch (Throwable ignored) { + // no-op + } + + // use JUL logger as a default logger + return tryLoadJdkProvider(); + } + + + private static LoggerProvider tryLoadJdkProvider() { + return new JdkLoggerProvider(); + } + + private static LoggerProvider tryLoadSlf4jProvider() { + return new Slf4jLoggerProvider(); + } + + private LoggerFactory() { + } + + /** + * Gets a logger provided by this factory. + * + * @param name logger name + * + * @return obtained logger + */ + public static Logger getLogger(String name) { + return loggerProvider.getLogger(name); + } + + /** + * Gets a logger provided by this factory. + * + * @param type target class + * + * @return obtained logger + */ + public static Logger getLogger(Class type) { + return loggerProvider.getLogger(type.getName()); + } + +} diff --git a/src/main/java/org/tarantool/logging/LoggerProvider.java b/src/main/java/org/tarantool/logging/LoggerProvider.java new file mode 100644 index 00000000..d356723b --- /dev/null +++ b/src/main/java/org/tarantool/logging/LoggerProvider.java @@ -0,0 +1,21 @@ +package org.tarantool.logging; + +/** + * Provides loggers depended on logger implementations. + * This interface is also used as SPI and its implementations + * can be discovered when {@link LoggerFactory} is loaded. + *

    + * This class is not a part of public API. + */ +public interface LoggerProvider { + + /** + * Gets a logger by its name. + * + * @param name logger name + * + * @return constructed logger + */ + Logger getLogger(final String name); + +} diff --git a/src/main/java/org/tarantool/logging/Slf4jLogger.java b/src/main/java/org/tarantool/logging/Slf4jLogger.java new file mode 100644 index 00000000..a3ba92e7 --- /dev/null +++ b/src/main/java/org/tarantool/logging/Slf4jLogger.java @@ -0,0 +1,69 @@ +package org.tarantool.logging; + +import java.text.MessageFormat; + +/** + * Adapter to SLF4J and Logback logger backends. + *

    + * This class is not a part of public API. + */ +class Slf4jLogger extends BaseLogger { + + private final org.slf4j.Logger delegate; + + public Slf4jLogger(String loggerName) { + delegate = org.slf4j.LoggerFactory.getLogger(loggerName); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public void log(Level level, String text, Throwable error, Object... parameters) { + if (!isLoggable(level)) { + return; + } + + String message = (parameters == null || parameters.length == 0) ? text : MessageFormat.format(text, parameters); + switch (level) { + case ERROR: + delegate.error(message, error); + break; + case WARN: + delegate.warn(message, error); + break; + case INFO: + delegate.info(message, error); + break; + case DEBUG: + delegate.debug(message, error); + break; + case TRACE: + delegate.trace(message, error); + break; + default: + break; + } + } + + @Override + public boolean isLoggable(Level level) { + switch (level) { + case ERROR: + return delegate.isErrorEnabled(); + case WARN: + return delegate.isWarnEnabled(); + case INFO: + return delegate.isInfoEnabled(); + case DEBUG: + return delegate.isDebugEnabled(); + case TRACE: + return delegate.isTraceEnabled(); + default: + return true; + } + } + +} diff --git a/src/main/java/org/tarantool/logging/Slf4jLoggerProvider.java b/src/main/java/org/tarantool/logging/Slf4jLoggerProvider.java new file mode 100644 index 00000000..f3e38119 --- /dev/null +++ b/src/main/java/org/tarantool/logging/Slf4jLoggerProvider.java @@ -0,0 +1,16 @@ +package org.tarantool.logging; + +/** + * Delegates a logger creation to SLF4J logger + * adapter. + *

    + * This class is not a part of public API. + */ +public class Slf4jLoggerProvider implements LoggerProvider { + + @Override + public Logger getLogger(String name) { + return new Slf4jLogger(name); + } + +} diff --git a/src/test/java/org/tarantool/ClientReconnectClusterIT.java b/src/test/java/org/tarantool/ClientReconnectClusterIT.java index f46031fa..6e1d005d 100644 --- a/src/test/java/org/tarantool/ClientReconnectClusterIT.java +++ b/src/test/java/org/tarantool/ClientReconnectClusterIT.java @@ -17,6 +17,7 @@ import java.net.ConnectException; import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -24,6 +25,8 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -84,6 +87,48 @@ public void tearDownTest() { stopInstancesAndAwait(SRV1, SRV2, SRV3); } + @Test + @DisplayName("requests were re-issued after reconnection") + public void testRetriesOnReconnect() throws ExecutionException, InterruptedException { + CyclicBarrier barrier = new CyclicBarrier(2); + TarantoolClusterClientConfig config = makeDefaultClusterClientConfig(); + config.operationExpiryTimeMillis = 3_000; + TarantoolClusterClient client = new TarantoolClusterClient( + config, + "localhost:" + PORTS[0], + "127.0.0.1:" + PORTS[1], + "localhost:" + PORTS[2] + ) { + boolean notFirst; + + @Override + protected void reconnect(Throwable lastError) { + if (notFirst) { + tryAwait(barrier); + } + notFirst = true; + super.reconnect(lastError); + } + }; + + stopInstancesAndAwait(SRV1); + + List> futures = new ArrayList<>(); + futures.add(client.asyncOps().eval("return 1+1")); + futures.add(client.asyncOps().eval("return 1+2")); + futures.add(client.asyncOps().eval("return 1+3")); + futures.add(client.asyncOps().eval("return 1+4")); + + tryAwait(barrier); + + for (Future future : futures) { + future.get(); + } + + stopInstancesAndAwait(SRV2); + stopInstancesAndAwait(SRV3); + } + @Test @DisplayName("reconnected to another node after the current node had disappeared") public void testRoundRobinReconnect() { diff --git a/src/test/java/org/tarantool/TestSocketChannelProvider.java b/src/test/java/org/tarantool/TestSocketChannelProvider.java index 44465288..6e417578 100644 --- a/src/test/java/org/tarantool/TestSocketChannelProvider.java +++ b/src/test/java/org/tarantool/TestSocketChannelProvider.java @@ -3,6 +3,7 @@ import static java.net.StandardSocketOptions.SO_LINGER; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.nio.channels.SocketChannel; /** @@ -37,7 +38,7 @@ public SocketChannel get(int retryNumber, Throwable lastError) { * default behaviour). */ channel.setOption(SO_LINGER, soLinger); - channel.connect(new InetSocketAddress(host, port)); + channel.connect(getAddress()); return channel; } catch (Exception e) { if (budget < System.currentTimeMillis()) { @@ -53,4 +54,9 @@ public SocketChannel get(int retryNumber, Throwable lastError) { } throw new RuntimeException(new InterruptedException()); } + + @Override + public SocketAddress getAddress() { + return new InetSocketAddress(host, port); + } } diff --git a/src/test/resources/jul-silent.properties b/src/test/resources/jul-silent.properties new file mode 100644 index 00000000..eb2f6fc5 --- /dev/null +++ b/src/test/resources/jul-silent.properties @@ -0,0 +1,9 @@ +# This configurations defines a standard console output +# and disables all messages for it by default + +handlers = java.util.logging.ConsoleHandler +.level = INFO +java.util.logging.ConsoleHandler.level = OFF +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter +# SimpleFormatter uses String.format(format, date, source, logger, level, message, thrown); +java.util.logging.SimpleFormatter.format = [%1$tF %1$tT] [%4$-7s] %3$s %5$s%6$s %n