diff --git a/java/jdbc/SessionlessTransactions/.gitignore b/java/jdbc/SessionlessTransactions/.gitignore new file mode 100644 index 00000000..7ed0d6b6 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/.gitignore @@ -0,0 +1,32 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/java/jdbc/SessionlessTransactions/LICENSE.txt b/java/jdbc/SessionlessTransactions/LICENSE.txt new file mode 100644 index 00000000..8dc7c070 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/LICENSE.txt @@ -0,0 +1,35 @@ +Copyright (c) 2025 Oracle and/or its affiliates. + +The Universal Permissive License (UPL), Version 1.0 + +Subject to the condition set forth below, permission is hereby granted to any +person obtaining a copy of this software, associated documentation and/or data +(collectively the "Software"), free of charge and under any and all copyright +rights in the Software, and any and all patent rights owned or freely +licensable by each licensor hereunder covering either (i) the unmodified +Software as contributed to or provided by such licensor, or (ii) the Larger +Works (as defined below), to deal in both + +(a) the Software, and +(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +one is included with the Software (each a "Larger Work" to which the Software +is contributed by such licensors), + +without restriction, including without limitation the rights to copy, create +derivative works of, display, perform, and distribute the Software and make, +use, sell, offer for sale, import, export, have made, and have sold the +Software and the Larger Work(s), and to sublicense the foregoing rights on +either these or other terms. + +This license is subject to the following condition: +The above copyright notice and either this complete permission notice or at +a minimum a reference to the UPL must be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/java/jdbc/SessionlessTransactions/README.md b/java/jdbc/SessionlessTransactions/README.md new file mode 100644 index 00000000..2cd6bb52 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/README.md @@ -0,0 +1,30 @@ +## Overview + +This project demonstrates the use of sessionless transactions feature of Oracle Database using JDBC. + +The project is a web service that handles booking requests. + +## Building and testing + +Create a test user: + +~~~SQL +CREATE USER test_user IDENTIFIED BY test_password; +GRANT CREATE SESSION TO test_user; +GRANT CREATE TABLE TO test_user; +GRANT CREATE SEQUENCE TO test_user; +GRANT DROP ANY TABLE TO test_user; +GRANT UNLIMITED TABLESPACE TO test_user; +~~~ + +Please set the database URL as an environment variable before running the application. + +**Example:** +~~~ +export TEST_DATABASE_URL=jdbc:oracle:thin:@localhost:5221:orcl +~~~ + +**Build and run endpoint tests:** +~~~ +mvn install +~~~ \ No newline at end of file diff --git a/java/jdbc/SessionlessTransactions/mvnw b/java/jdbc/SessionlessTransactions/mvnw new file mode 100755 index 00000000..66df2854 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + 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 + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# 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. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + 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 + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/java/jdbc/SessionlessTransactions/mvnw.cmd b/java/jdbc/SessionlessTransactions/mvnw.cmd new file mode 100644 index 00000000..95ba6f54 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/mvnw.cmd @@ -0,0 +1,205 @@ +@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 https://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 Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@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 keystroke 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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\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 WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_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 WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_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('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@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 "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\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% + +cmd /C exit /B %ERROR_CODE% diff --git a/java/jdbc/SessionlessTransactions/pom.xml b/java/jdbc/SessionlessTransactions/pom.xml new file mode 100644 index 00000000..3de56f1a --- /dev/null +++ b/java/jdbc/SessionlessTransactions/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.2 + + + com.oracle.jdbc.samples + sessionlesstxns + 0.0.1-SNAPSHOT + SessionlessTransactionsDemo + Sessionless transactions demo + + 17 + 23.6.0.24.10 + + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + + org.springframework.boot + spring-boot-starter-web + + + + com.oracle.database.jdbc + ojdbc17 + ${ojdbc.version} + + + + com.oracle.database.jdbc + ucp17 + ${ojdbc.version} + + + + com.oracle.database.ha + ons + ${ojdbc.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + io.rest-assured + rest-assured + 5.5.1 + test + + + diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/SessionlessTransactionsDemo.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/SessionlessTransactionsDemo.java new file mode 100644 index 00000000..e5ddbc40 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/SessionlessTransactionsDemo.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SessionlessTransactionsDemo { + public static void main(String[] args) { + SpringApplication.run(SessionlessTransactionsDemo.class, args); + } +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/controller/BookingController.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/controller/BookingController.java new file mode 100644 index 00000000..24855e40 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/controller/BookingController.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.controller; + +import com.oracle.jdbc.samples.sessionlesstxns.dto.CheckoutRequest; +import com.oracle.jdbc.samples.sessionlesstxns.dto.CheckoutResponse; +import com.oracle.jdbc.samples.sessionlesstxns.dto.RemoveTicketRequest; +import com.oracle.jdbc.samples.sessionlesstxns.dto.StartTransactionRequest; +import com.oracle.jdbc.samples.sessionlesstxns.dto.RequestTicketsRequest; +import com.oracle.jdbc.samples.sessionlesstxns.dto.RequestTicketsResponse; +import com.oracle.jdbc.samples.sessionlesstxns.dto.StartTransactionResponse; +import com.oracle.jdbc.samples.sessionlesstxns.service.BookingService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +@CrossOrigin(originPatterns = "http://localhost:**") +@Controller +@RequestMapping("/api/v1/bookings") +public class BookingController { + + BookingService bookingService; + + public BookingController(BookingService bookingService) { + this.bookingService = bookingService; + } + + /** + * Start a new transaction, and request first flight ticket(s). + */ + @PostMapping + public ResponseEntity startTransaction(@RequestBody StartTransactionRequest body) { + + StartTransactionResponse response = bookingService.startTransaction(body); + + if (body.count() > response.count()) { + return new ResponseEntity<>(response, HttpStatus.PARTIAL_CONTENT); + } + + return new ResponseEntity<>(response, HttpStatus.CREATED); + } + + /** + * Resume transaction and request more tickets. + */ + @PostMapping(value = {"/{bookingId}"}) + public ResponseEntity requestTickets(@PathVariable Long bookingId, + @RequestBody RequestTicketsRequest body) { + RequestTicketsResponse response = bookingService.requestTickets(bookingId, body); + + if (body.count() > response.count()) { + return new ResponseEntity<>(response, HttpStatus.PARTIAL_CONTENT); + } + + return new ResponseEntity<>(response, HttpStatus.CREATED); + } + + /** + * Remove tickets from the booking order. + */ + @DeleteMapping(value = {"/{bookingId}"}) + public ResponseEntity removeTicket(@PathVariable Long bookingId, + @RequestBody RemoveTicketRequest body) { + bookingService.removeTicket(bookingId, body.seatId(), body.transactionId()); + + return new ResponseEntity<>(HttpStatus.OK); + } + + /** + * Pay and complete booking order. + */ + @PostMapping(value = {"/{bookingId}/checkout"}) + public ResponseEntity checkout(@PathVariable Long bookingId, + @RequestBody CheckoutRequest body) { + return new ResponseEntity<>(bookingService.checkout(bookingId, body), HttpStatus.CREATED); + } + + /** + * Cancel booking. + */ + @PostMapping(value = {"/cancel/{transactionId}"}) + public ResponseEntity cancelBooking(@PathVariable String transactionId) { + bookingService.cancelBooking(transactionId); + + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/controller/BookingControllerAdvice.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/controller/BookingControllerAdvice.java new file mode 100644 index 00000000..ddbb0454 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/controller/BookingControllerAdvice.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.controller; + +import com.oracle.jdbc.samples.sessionlesstxns.dto.ErrorResponse; +import com.oracle.jdbc.samples.sessionlesstxns.exception.APIException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class BookingControllerAdvice { + @ExceptionHandler({APIException.class}) + public ResponseEntity handleAPIException(APIException ex) { + ErrorResponse errorResponse = new ErrorResponse( + ex.getHttpStatus().value(), + ex.getHttpStatus().name(), + ex.getMessage()); + + return new ResponseEntity<>(errorResponse, ex.getHttpStatus()); + } +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/CheckoutRequest.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/CheckoutRequest.java new file mode 100644 index 00000000..d201804e --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/CheckoutRequest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.dto; + +public record CheckoutRequest( + String transactionId, + Long paymentMethod +) {} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/CheckoutResponse.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/CheckoutResponse.java new file mode 100644 index 00000000..53033a63 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/CheckoutResponse.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.dto; + +import java.util.List; + +public record CheckoutResponse( + Long id, + List tickets, + double total, + String receiptNumber, + Long paymentMethod +) { + public static record TicketDTO( + Long seatId, + Long flightId, + Float price + ) {} +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/ErrorResponse.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/ErrorResponse.java new file mode 100644 index 00000000..a949292b --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/ErrorResponse.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.dto; + +public record ErrorResponse( + int statusCode, + String error, + String message +) {} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/RemoveTicketRequest.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/RemoveTicketRequest.java new file mode 100644 index 00000000..14925f37 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/RemoveTicketRequest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.dto; + +public record RemoveTicketRequest( + String transactionId, + Long seatId +) {} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/RequestTicketsRequest.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/RequestTicketsRequest.java new file mode 100644 index 00000000..783db6c0 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/RequestTicketsRequest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.dto; + +public record RequestTicketsRequest( + String transactionId, + Long flightId, + int count +) {} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/RequestTicketsResponse.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/RequestTicketsResponse.java new file mode 100644 index 00000000..3f698304 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/RequestTicketsResponse.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.dto; + +import java.util.List; + +public record RequestTicketsResponse( + Integer count, + List seatIds +) {} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/StartTransactionRequest.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/StartTransactionRequest.java new file mode 100644 index 00000000..5a16388b --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/StartTransactionRequest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.dto; + +public record StartTransactionRequest( + int timeout, + long flightId, + int count +) {} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/StartTransactionResponse.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/StartTransactionResponse.java new file mode 100644 index 00000000..f00898fa --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/dto/StartTransactionResponse.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.dto; + +import java.util.List; + +public record StartTransactionResponse( + Long bookingId, + String transactionId, + Integer count, + List seatIds +) {} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/APIException.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/APIException.java new file mode 100644 index 00000000..aa6d8cf9 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/APIException.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.exception; + +import org.springframework.http.HttpStatus; + +public class APIException extends RuntimeException { + private HttpStatus httpStatus; + private String message; + + public APIException(HttpStatus httpsStatus, String message) { + super(message); + this.message = message; + this.httpStatus = httpsStatus; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + + public String getMessage() { + return message; + } +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/BookingNotFoundException.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/BookingNotFoundException.java new file mode 100644 index 00000000..617a00ca --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/BookingNotFoundException.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.exception; + +import org.springframework.http.HttpStatus; + +public class BookingNotFoundException extends APIException { + public BookingNotFoundException(long bookingId) { + super(HttpStatus.NOT_FOUND, "Booking " + bookingId + " not found."); + } +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/NoFreeSeatFoundException.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/NoFreeSeatFoundException.java new file mode 100644 index 00000000..b993d695 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/NoFreeSeatFoundException.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.exception; + +import org.springframework.http.HttpStatus; + +public class NoFreeSeatFoundException extends APIException { + public NoFreeSeatFoundException(long flightId) { + super(HttpStatus.NOT_FOUND, "No free seat found for flight: " + flightId); + } +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/NoTicketFoundException.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/NoTicketFoundException.java new file mode 100644 index 00000000..8e081cd6 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/NoTicketFoundException.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.exception; + +import org.springframework.http.HttpStatus; + +public class NoTicketFoundException extends APIException { + public NoTicketFoundException(long seatId, long bookingId) { + super(HttpStatus.NOT_FOUND, "Booking order " + bookingId + " does not have any tickets with seat " + seatId); + } +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/PaymentFailedException.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/PaymentFailedException.java new file mode 100644 index 00000000..d15aaa8f --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/PaymentFailedException.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.exception; + +import org.springframework.http.HttpStatus; + +public class PaymentFailedException extends APIException { + public PaymentFailedException() { + super(HttpStatus.PAYMENT_REQUIRED, "Payment failed"); + } +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/TransactionNotFoundException.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/TransactionNotFoundException.java new file mode 100644 index 00000000..e36da574 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/TransactionNotFoundException.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.exception; + +import org.springframework.http.HttpStatus; + +public class TransactionNotFoundException extends APIException { + + public TransactionNotFoundException(String transaction_id) { + super(HttpStatus.NOT_FOUND, "Transaction " + transaction_id + " not found"); + } +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/UnexpectedException.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/UnexpectedException.java new file mode 100644 index 00000000..57cc4890 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/exception/UnexpectedException.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.exception; + +import org.springframework.http.HttpStatus; + +public class UnexpectedException extends APIException { + public UnexpectedException(Throwable cause) { + super(HttpStatus.INTERNAL_SERVER_ERROR, "An unexpected error has occurred"); + initCause(cause); + } +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/service/BookingService.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/service/BookingService.java new file mode 100644 index 00000000..72c8da0b --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/service/BookingService.java @@ -0,0 +1,294 @@ +package com.oracle.jdbc.samples.sessionlesstxns.service; + +import com.oracle.jdbc.samples.sessionlesstxns.dto.CheckoutRequest; +import com.oracle.jdbc.samples.sessionlesstxns.dto.CheckoutResponse; +import com.oracle.jdbc.samples.sessionlesstxns.dto.StartTransactionRequest; +import com.oracle.jdbc.samples.sessionlesstxns.dto.RequestTicketsRequest; +import com.oracle.jdbc.samples.sessionlesstxns.dto.RequestTicketsResponse; +import com.oracle.jdbc.samples.sessionlesstxns.dto.StartTransactionResponse; +import com.oracle.jdbc.samples.sessionlesstxns.exception.APIException; +import com.oracle.jdbc.samples.sessionlesstxns.exception.BookingNotFoundException; +import com.oracle.jdbc.samples.sessionlesstxns.exception.NoFreeSeatFoundException; +import com.oracle.jdbc.samples.sessionlesstxns.exception.NoTicketFoundException; +import com.oracle.jdbc.samples.sessionlesstxns.exception.TransactionNotFoundException; +import com.oracle.jdbc.samples.sessionlesstxns.exception.UnexpectedException; +import com.oracle.jdbc.samples.sessionlesstxns.util.Util; +import oracle.jdbc.OracleConnection; +import oracle.jdbc.OraclePreparedStatement; +import org.springframework.stereotype.Service; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Savepoint; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +@Service +public class BookingService { + DataSource connectionPool; + PaymentService paymentService; + static final int INTEGRITY_CONSTRAINT_ERROR = 2291; + static final int TRANSACTION_NOT_FOUND_ERROR = 26218; + + public BookingService(DataSource dataSource, @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") PaymentService paymentService) { + this.connectionPool = dataSource; + this.paymentService = paymentService; + } + + public StartTransactionResponse startTransaction(StartTransactionRequest body) { + try (OracleConnection conn = (OracleConnection) connectionPool.getConnection(); + AutoCloseable rollback = conn::rollback;) { + conn.setAutoCommit(false); + byte[] gtrid = conn.startTransaction(body.timeout() * 60); + + long bookingId = createBooking(conn); + + List seats = lockAndBookSeats(conn, bookingId, body.flightId(), body.count()); + conn.suspendTransaction(); + + return new StartTransactionResponse(bookingId, Util.byteArrayToHex(gtrid), seats.size(), seats); + } catch (APIException ex) { + throw ex; + } catch (Exception ex) { + throw new UnexpectedException(ex); + } + } + + public RequestTicketsResponse requestTickets(Long bookingId, RequestTicketsRequest body) { + try (OracleConnection conn = (OracleConnection) connectionPool.getConnection(); + AutoCloseable suspend = conn::suspendTransaction;) { + conn.setAutoCommit(false); + + conn.resumeTransaction(Util.hexToByteArray(body.transactionId())); + + List seats = lockAndBookSeats(conn, bookingId, body.flightId(), body.count()); + + return new RequestTicketsResponse(seats.size(), seats); + } catch (SQLException ex) { + if (ex.getErrorCode() == TRANSACTION_NOT_FOUND_ERROR) { + throw new TransactionNotFoundException(body.transactionId()); + } + throw new UnexpectedException(ex); + } catch (APIException ex) { + throw ex; + } catch (Exception ex) { + throw new UnexpectedException(ex); + } + } + + public void removeTicket(Long bookingId, Long seatId, String transactionId) { + try (OracleConnection conn = (OracleConnection) connectionPool.getConnection(); + AutoCloseable suspend = conn::suspendTransaction;) { + conn.setAutoCommit(false); + conn.resumeTransaction(Util.hexToByteArray(transactionId)); + + removeTicket(conn, seatId, bookingId); + } catch (SQLException ex) { + if (ex.getErrorCode() == TRANSACTION_NOT_FOUND_ERROR) { + throw new TransactionNotFoundException(transactionId); + } + throw new UnexpectedException(ex); + } catch (APIException ex) { + throw ex; + } catch (Exception ex) { + throw new UnexpectedException(ex); + } + } + + public CheckoutResponse checkout(Long bookingId, CheckoutRequest checkoutDetails) { + float sum; + byte[] gtrid = Util.hexToByteArray(checkoutDetails.transactionId()); + String receipt; + List tickets; + + try (OracleConnection conn = (OracleConnection) connectionPool.getConnection();) { + conn.setAutoCommit(false); + conn.resumeTransaction(gtrid); + try { + tickets = getTickets(conn, bookingId); + sum = tickets.stream().map(CheckoutResponse.TicketDTO::price).reduce(0F, Float::sum); + receipt = paymentService.pay(sum, checkoutDetails.paymentMethod()); + saveReceipt(conn, receipt, sum, bookingId, checkoutDetails.paymentMethod()); + } catch (Exception ex) { + conn.suspendTransaction(); + throw ex; + } + conn.commit(); + } catch (SQLException ex) { + if (ex.getErrorCode() == TRANSACTION_NOT_FOUND_ERROR) { + throw new TransactionNotFoundException(checkoutDetails.transactionId()); + } + throw new UnexpectedException(ex); + } catch (APIException ex) { + throw ex; + } catch (Exception ex) { + throw new UnexpectedException(ex); + } + + return new CheckoutResponse(bookingId, tickets, sum, receipt, checkoutDetails.paymentMethod()); + } + + public void cancelBooking(String transactionId) { + try (OracleConnection conn = (OracleConnection) connectionPool.getConnection();) { + conn.setAutoCommit(false); + conn.resumeTransaction(Util.hexToByteArray(transactionId)); + conn.rollback(); + } catch (SQLException ex) { + if (ex.getErrorCode() == TRANSACTION_NOT_FOUND_ERROR) { + throw new TransactionNotFoundException(transactionId); + } + throw new UnexpectedException(ex); + } catch (APIException ex) { + throw ex; + } catch (Exception ex) { + throw new UnexpectedException(ex); + } + } + + private List lockAndBookSeats(OracleConnection conn, long bookingId, long flightId, int count) + throws SQLException, NoFreeSeatFoundException, BookingNotFoundException { + Savepoint sp1 = conn.setSavepoint(); + try { + List seats = getFreeSeats(conn, flightId, count); + if (seats.isEmpty()) { + throw new NoFreeSeatFoundException(flightId); + } + for (Long seatId : seats) { + addTicket(conn, seatId, bookingId); + } + return seats; + } catch (BookingNotFoundException ex) { + conn.rollback(sp1); + throw ex; + } + } + + private void saveReceipt(OracleConnection conn, String receiptNumber, double sum, long bookingId, long paymentMethodId) + throws SQLException { + final String saveReceiptDML = """ + INSERT INTO receipts (created_at, receipt_number, total, booking_id, payment_method_id) values (?, ?, ?, ?, ?); + """; + + try (OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement(saveReceiptDML)) { + stmt.setTimestamp(1, new Timestamp(System.currentTimeMillis())); + stmt.setString(2, receiptNumber); + stmt.setDouble(3, sum); + stmt.setLong(4, bookingId); + stmt.setLong(5, paymentMethodId); + stmt.execute(); + } catch (SQLException ex) { + if (ex.getErrorCode() == INTEGRITY_CONSTRAINT_ERROR) { + throw new BookingNotFoundException(bookingId); + } + throw ex; + } + } + + private void addTicket(Connection conn, long seatId, long bookingId) + throws SQLException, BookingNotFoundException { + final String saveTicketDML = "INSERT INTO tickets (seat_id, booking_id) VALUES (?, ?)"; + try (PreparedStatement stmt = conn.prepareStatement(saveTicketDML)) { + stmt.setLong(1, seatId); + stmt.setLong(2, bookingId); + stmt.execute(); + } catch (SQLException ex) { + if (ex.getErrorCode() == INTEGRITY_CONSTRAINT_ERROR) { + throw new BookingNotFoundException(bookingId); + } + throw ex; + } + + final String updateSeatDML = "UPDATE seats SET available=FALSE WHERE id = ?"; + try(PreparedStatement stmt = conn.prepareStatement(updateSeatDML)) { + stmt.setLong(1, seatId); + stmt.execute(); + } + } + + private void removeTicket(OracleConnection conn, long seatId, long bookingId) + throws SQLException, NoTicketFoundException { + final String deleteTicketDML = "DELETE FROM tickets WHERE seat_id = ? AND booking_id = ?"; + try (PreparedStatement stmt = conn.prepareStatement(deleteTicketDML)) { + stmt.setLong(1, seatId); + stmt.setLong(2, bookingId); + if (stmt.executeUpdate() == 0) { + throw new NoTicketFoundException(seatId, bookingId); + } + } + + final String updateSeatDML = "UPDATE seats SET available=TRUE WHERE id = ?"; + try (PreparedStatement stmt = conn.prepareStatement(updateSeatDML)) { + stmt.setLong(1, seatId); + stmt.executeUpdate(); + } + } + + /** + * Get free tickets from the database and lock them. + * + * @param conn db connection. + * @param flightId flight ID. + * @param count number of tickets to request. + * + * @return list of locked tickets, list size will be less or equal to {@code count} depending on the + * availability of the tickets in the database. + */ + private List getFreeSeats(OracleConnection conn, long flightId, int count) throws SQLException { + final String getFreeSeatsQuery = """ + SELECT id FROM seats + WHERE available = true AND flight_id = ? + FETCH FIRST ? ROW ONLY + FOR UPDATE SKIP LOCKED;"""; + + List seats = new ArrayList<>(); + + try(PreparedStatement stmt = conn.prepareStatement(getFreeSeatsQuery);) { + stmt.setLong(1, flightId); + stmt.setInt(2, count); + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + seats.add(rs.getLong(1)); + } + } + + return seats; + } + + private long createBooking(OracleConnection conn) throws SQLException { + final String createBookingDML = "INSERT INTO bookings (created_at) VALUES (NULL) RETURNING ID INTO ?"; + try (OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement(createBookingDML)) { + stmt.registerReturnParameter(1, java.sql.Types.NUMERIC); + stmt.execute(); + ResultSet rs = stmt.getReturnResultSet(); + + rs.next(); + return rs.getLong(1); + } + } + + + private List getTickets(OracleConnection conn, long bookingId) throws SQLException { + final String ticketsQuery = """ + SELECT t1.seat_id, t2.flight_id, t3.price + FROM tickets t1 + JOIN seats t2 ON t1.seat_id = t2.id + JOIN flights t3 ON t2.flight_id = t3.id + WHERE t1.booking_id = ? + """; + List tickets = new ArrayList<>(); + + try (PreparedStatement stmt = conn.prepareStatement(ticketsQuery)) { + stmt.setLong(1, bookingId); + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + tickets.add(new CheckoutResponse.TicketDTO(rs.getLong(1), rs.getLong(2), rs.getFloat(3))); + } + } + + return tickets; + } +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/service/PaymentService.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/service/PaymentService.java new file mode 100644 index 00000000..d88fa66b --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/service/PaymentService.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.service; + +import com.oracle.jdbc.samples.sessionlesstxns.exception.PaymentFailedException; +import org.springframework.stereotype.Service; + +@Service +public interface PaymentService { + + /** + * @param sum + * @param paymentMethodId + * @return receipt number. + */ + String pay(double sum, long paymentMethodId) throws PaymentFailedException; +} diff --git a/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/util/Util.java b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/util/Util.java new file mode 100644 index 00000000..d4519882 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/util/Util.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.jdbc.samples.sessionlesstxns.util; + +public class Util { + public static String byteArrayToHex(byte[] a) { + if (a == null) return null; + + StringBuilder sb = new StringBuilder(a.length * 2); + for(byte b: a) + sb.append(String.format("%02x", b)); + return sb.toString().toUpperCase(); + } + + public static byte[] hexToByteArray(String s) { + int size = s.length(); + if (size % 2 != 0) { + throw new IllegalArgumentException("String must have an even number of characters"); + } + + byte[] array = new byte[size / 2]; + for (int i = 0; i < size; i += 2) { + array[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); + } + + return array; + } +} diff --git a/java/jdbc/SessionlessTransactions/src/main/resources/application.properties b/java/jdbc/SessionlessTransactions/src/main/resources/application.properties new file mode 100644 index 00000000..d538e532 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/main/resources/application.properties @@ -0,0 +1,50 @@ +# +# Copyright (c) 2024 Oracle and/or its affiliates. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or data +# (collectively the "Software"), free of charge and under any and all copyright +# rights in the Software, and any and all patent rights owned or freely +# licensable by each licensor hereunder covering either (i) the unmodified +# Software as contributed to or provided by such licensor, or (ii) the Larger +# Works (as defined below), to deal in both +# +# (a) the Software, and +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software (each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# The above copyright notice and either this complete permission notice or at +# a minimum a reference to the UPL must be included in all copies or +# substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +# Configuring UCP datasource +spring.datasource.url=${DATABASE_URL} +spring.datasource.username=${DATABASE_USERNAME} +spring.datasource.password=${DATABASE_PASSWORD} +spring.datasource.driver-class-name=oracle.jdbc.OracleDriver +spring.datasource.type=oracle.ucp.jdbc.PoolDataSource +spring.datasource.oracleucp.connection-factory-class-name=oracle.jdbc.pool.OracleDataSource +spring.datasource.oracleucp.sql-for-validate-connection=select * from dual +spring.datasource.oracleucp.connection-pool-name=connectionPoolName1 +spring.datasource.oracleucp.initial-pool-size=15 +spring.datasource.oracleucp.min-pool-size=5 +spring.datasource.oracleucp.max-pool-size=30 diff --git a/java/jdbc/SessionlessTransactions/src/test/java/com/oracle/jdbc/samples/sessionlesstxns/TestApis.java b/java/jdbc/SessionlessTransactions/src/test/java/com/oracle/jdbc/samples/sessionlesstxns/TestApis.java new file mode 100644 index 00000000..05afc4cc --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/test/java/com/oracle/jdbc/samples/sessionlesstxns/TestApis.java @@ -0,0 +1,298 @@ +package com.oracle.jdbc.samples.sessionlesstxns; + +import com.oracle.jdbc.samples.sessionlesstxns.dto.CheckoutRequest; +import com.oracle.jdbc.samples.sessionlesstxns.dto.CheckoutResponse; +import com.oracle.jdbc.samples.sessionlesstxns.dto.RemoveTicketRequest; +import com.oracle.jdbc.samples.sessionlesstxns.dto.StartTransactionRequest; +import com.oracle.jdbc.samples.sessionlesstxns.dto.RequestTicketsRequest; +import com.oracle.jdbc.samples.sessionlesstxns.dto.RequestTicketsResponse; +import com.oracle.jdbc.samples.sessionlesstxns.dto.StartTransactionResponse; +import com.oracle.jdbc.samples.sessionlesstxns.exception.PaymentFailedException; +import com.oracle.jdbc.samples.sessionlesstxns.service.PaymentService; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.*; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.http.HttpStatus; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ActiveProfiles; + +import javax.sql.DataSource; +import java.io.InputStream; +import java.util.List; +import java.util.Scanner; + +@ActiveProfiles("test") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestApis { + + static final long FLIGHT1_ID = 0; + static final int FLIGHT1_AVAILABLE_SEATS = 1; + static final long FLIGHT1_PRICE = 1000; + static final long FLIGHT2_ID = 1; + static final long FLIGHT2_PRICE = 2300; + static final long FLIGHT3_ID = 2; + static final long PAYMENT_METHOD_ID = 0; + static final String DUMMY_RECEIPT_NUMBER = "1442AZ"; + static final int DEFAULT_TIMEOUT = 1; + + static { + RestAssured.baseURI = "http://127.0.0.1"; + } + + @LocalServerPort + private int port; + + @BeforeAll + void setUp() { + RestAssured.port = port; + createSchema(); + } + + @AfterAll + void tearDown() { + dropSchema(); + } + + @BeforeEach + void updateData() { + cleanTables(); + loadData(); + } + + @Autowired + protected JdbcTemplate testJdbcTemplate; + + @Autowired + protected DataSource dataSource; + + static protected PaymentService paymentService = Mockito.mock(PaymentService.class); + + @TestConfiguration + static class TestConfig { + @Bean + @Primary + public PaymentService paymentService() { + return paymentService; + } + } + + /** + * Test following scenario: + * + * 1. Start a transaction. + * 2. Add 1 ticket of flight 1. + * 3. Add 2 tickets of flight 2. + * 4. Checkout. + */ + @Test + void testNormalScenario() { + Mockito.when(paymentService.pay(Mockito.anyDouble(),Mockito.eq(PAYMENT_METHOD_ID))) + .thenReturn(DUMMY_RECEIPT_NUMBER); + + final int FLIGHT1_REQUESTED_SEATS = 1; + var startTransaction = testAPIStartTransaction(DEFAULT_TIMEOUT, FLIGHT1_ID, FLIGHT1_REQUESTED_SEATS, HttpStatus.CREATED); + + Assertions.assertNotNull(startTransaction); + Assertions.assertNotNull(startTransaction.bookingId()); + Assertions.assertEquals(FLIGHT1_REQUESTED_SEATS, startTransaction.count()); + Assertions.assertEquals(FLIGHT1_REQUESTED_SEATS, startTransaction.seatIds().size()); + + final int FLIGHT2_REQUESTED_SEATS = 2; + var requestTickets = testAPIRequestTickets( + startTransaction.transactionId(), FLIGHT2_ID, FLIGHT2_REQUESTED_SEATS, startTransaction.bookingId(), HttpStatus.CREATED); + + Assertions.assertNotNull(requestTickets); + Assertions.assertEquals(FLIGHT2_REQUESTED_SEATS, requestTickets.count()); + Assertions.assertEquals(FLIGHT2_REQUESTED_SEATS, requestTickets.seatIds().size()); + + var checkout = testAPICheckout(startTransaction.transactionId(), PAYMENT_METHOD_ID, startTransaction.bookingId(), HttpStatus.CREATED); + + Assertions.assertNotNull(checkout); + Assertions.assertEquals(FLIGHT1_REQUESTED_SEATS * FLIGHT1_PRICE + FLIGHT2_REQUESTED_SEATS * FLIGHT2_PRICE, checkout.total()); + Assertions.assertEquals(PAYMENT_METHOD_ID, checkout.paymentMethod()); + Assertions.assertEquals(DUMMY_RECEIPT_NUMBER, checkout.receiptNumber()); + Assertions.assertNotNull(checkout.tickets()); + Assertions.assertEquals(FLIGHT1_REQUESTED_SEATS + FLIGHT2_REQUESTED_SEATS, checkout.tickets().size()); + + final String queryBookingOrder = "SELECT id FROM bookings WHERE id = ?"; + Assertions.assertNotNull(testJdbcTemplate.queryForObject(queryBookingOrder, Long.class, startTransaction.bookingId())); + } + + /** + * Test following scenario: + * - Add 2 tickets of flight 1 (only one found) + * - Add 1 tickets of flight 3 (not free seat found) + * - cancel booking + */ + @Test + void secondScenario() { + final int FLIGHT1_REQUESTED_SEATS = 2; + var startTransaction = testAPIStartTransaction(DEFAULT_TIMEOUT, FLIGHT1_ID, FLIGHT1_REQUESTED_SEATS, HttpStatus.PARTIAL_CONTENT); + + Assertions.assertNotNull(startTransaction); + Assertions.assertNotNull(startTransaction.bookingId()); + Assertions.assertEquals(FLIGHT1_AVAILABLE_SEATS, startTransaction.count()); + Assertions.assertEquals(FLIGHT1_AVAILABLE_SEATS, startTransaction.seatIds().size()); + + final int FLIGHT3_REQUESTED_SEATS = 1; + testAPIRequestTickets(startTransaction.transactionId(), FLIGHT3_ID, FLIGHT3_REQUESTED_SEATS, startTransaction.bookingId(), HttpStatus.NOT_FOUND); + + testAPICancelBooking(startTransaction.transactionId(), HttpStatus.OK); + } + + /** + * Test following scenario: + * - Request 2 seats of flight 2 + * - Checkout (payment fails) + * - Remove ticket of flight 2 + * - Checkout (payment succeeds) + */ + @Test + void thirdScenario() { + Mockito.when(paymentService.pay(Mockito.anyDouble(),Mockito.eq(PAYMENT_METHOD_ID))).thenAnswer(invocation -> { + double sum = invocation.getArgument(0); + if (sum > FLIGHT2_PRICE) { + throw new PaymentFailedException(); + } + return DUMMY_RECEIPT_NUMBER; + }); + + final int FLIGHT2_REQUESTED_SEATS = 2; + var startTransaction = testAPIStartTransaction(DEFAULT_TIMEOUT, FLIGHT2_ID, FLIGHT2_REQUESTED_SEATS, HttpStatus.CREATED); + + testAPICheckout(startTransaction.transactionId(), PAYMENT_METHOD_ID, startTransaction.bookingId(), HttpStatus.PAYMENT_REQUIRED); + + final long SEAT_ID_TO_CANCEL = startTransaction.seatIds().get(0); + testAPIRemoveTicket(startTransaction.transactionId(), startTransaction.bookingId(), SEAT_ID_TO_CANCEL, HttpStatus.OK); + + final int NEW_REQUESTED_SEATS_COUNT = FLIGHT2_REQUESTED_SEATS - 1; + var checkout = testAPICheckout(startTransaction.transactionId(), PAYMENT_METHOD_ID, startTransaction.bookingId(), HttpStatus.CREATED); + + Assertions.assertNotNull(checkout); + Assertions.assertEquals(NEW_REQUESTED_SEATS_COUNT * FLIGHT2_PRICE, checkout.total()); + Assertions.assertEquals(DUMMY_RECEIPT_NUMBER, checkout.receiptNumber()); + Assertions.assertNotNull(checkout.tickets()); + Assertions.assertEquals(NEW_REQUESTED_SEATS_COUNT, checkout.tickets().size()); + Assertions.assertTrue(checkout.tickets().stream().noneMatch(ticket -> ticket.seatId().equals(SEAT_ID_TO_CANCEL))); + } + + private void createSchema() { + runSQLScript("createSchema.sql"); + } + + private void loadData() { + runSQLScript("dataLoader.sql"); + } + + private void dropSchema() { + runSQLScript("dropSchema.sql"); + } + + private void cleanTables() { + runSQLScript("dataCleaner.sql"); + } + + private void runSQLScript(String fileName) { + InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName); + List instructions = new Scanner(inputStream).useDelimiter(";").tokens().toList(); + for (String sql : instructions) { + testJdbcTemplate.update(sql); + } + } + + private StartTransactionResponse testAPIStartTransaction(int timeout, long flightId, int count, HttpStatus expectedStatus) { + var request = RestAssured.given() + .contentType(ContentType.JSON) + .body(new StartTransactionRequest(timeout, flightId, count)) + .when() + .post("/api/v1/bookings"); + + if (expectedStatus.equals(HttpStatus.CREATED) || expectedStatus.equals(HttpStatus.PARTIAL_CONTENT)) { + return request + .then() + .statusCode(expectedStatus.value()) + .and() + .extract() + .response() + .body().as(StartTransactionResponse.class); + } + + request.then().statusCode(expectedStatus.value()); + return null; + } + + private RequestTicketsResponse testAPIRequestTickets( + String transactionId, long flightId, int count, long bookingId, HttpStatus expectedStatus) { + var request = RestAssured.given() + .contentType(ContentType.JSON) + .body(new RequestTicketsRequest(transactionId, flightId, count)) + .pathParam("bookingId", bookingId) + .when() + .post("/api/v1/bookings/{bookingId}"); + + + if (expectedStatus.equals(HttpStatus.CREATED) || expectedStatus.equals(HttpStatus.PARTIAL_CONTENT)) { + return request + .then() + .statusCode(expectedStatus.value()) + .and() + .extract() + .response() + .body().as(RequestTicketsResponse.class); + } + + request.then().statusCode(expectedStatus.value()); + return null; + } + + private void testAPIRemoveTicket(String transactionId, long bookingId, long seatId, HttpStatus expectedStatus) { + RestAssured.given() + .contentType(ContentType.JSON) + .body(new RemoveTicketRequest(transactionId, seatId)) + .pathParams("bookingId", bookingId) + .when() + .delete("/api/v1/bookings/{bookingId}") + .then() + .statusCode(expectedStatus.value()); + } + + private CheckoutResponse testAPICheckout( + String transactionId, long paymentMethodId, long bookingId, HttpStatus expectedStatus) { + var request = RestAssured.given() + .contentType(ContentType.JSON) + .body(new CheckoutRequest(transactionId, paymentMethodId)) + .pathParam("bookingId", bookingId) + .when() + .post("/api/v1/bookings/{bookingId}/checkout"); + + if (expectedStatus.equals(HttpStatus.CREATED)) { + return request + .then() + .statusCode(HttpStatus.CREATED.value()) + .and() + .extract() + .response() + .body().as(CheckoutResponse.class); + } + + request.then().statusCode(expectedStatus.value()); + return null; + } + + private void testAPICancelBooking(String transactionId, HttpStatus expectedStatus) { + RestAssured.given() + .contentType(ContentType.JSON) + .pathParam("transactionId", transactionId) + .when() + .post("/api/v1/bookings/cancel/{transactionId}") + .then().statusCode(expectedStatus.value()); + } +} diff --git a/java/jdbc/SessionlessTransactions/src/test/resources/application.properties b/java/jdbc/SessionlessTransactions/src/test/resources/application.properties new file mode 100644 index 00000000..706e93b9 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/test/resources/application.properties @@ -0,0 +1,12 @@ +# src/test/resources/application.yml +spring.datasource.url=${TEST_DATABASE_URL} +spring.datasource.username=test_user +spring.datasource.password=test_password +spring.datasource.driver-class-name=oracle.jdbc.OracleDriver +spring.datasource.type=oracle.ucp.jdbc.PoolDataSource +spring.datasource.oracleucp.connection-factory-class-name=oracle.jdbc.pool.OracleDataSource +spring.datasource.oracleucp.sql-for-validate-connection=select * from dual +spring.datasource.oracleucp.connection-pool-name=connectionPoolName1 +spring.datasource.oracleucp.initial-pool-size=15 +spring.datasource.oracleucp.min-pool-size=10 +spring.datasource.oracleucp.max-pool-size=30 \ No newline at end of file diff --git a/java/jdbc/SessionlessTransactions/src/test/resources/createSchema.sql b/java/jdbc/SessionlessTransactions/src/test/resources/createSchema.sql new file mode 100644 index 00000000..bc0d6dd3 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/test/resources/createSchema.sql @@ -0,0 +1,49 @@ +CREATE TABLE bookings ( + id NUMBER GENERATED ALWAYS AS IDENTITY, + created_at DATE, + CONSTRAINT BOOKING_PK PRIMARY KEY (id) +); + +CREATE TABLE flights ( + id NUMBER, + flight_number VARCHAR(255), + origin VARCHAR(255), + destination VARCHAR(255), + departure TIMESTAMP, + price NUMBER, + CONSTRAINT FLIGHT_PK PRIMARY KEY (id) +); + +CREATE TABLE seats ( + id NUMBER, + flight_id NUMBER NOT NULL, + available BOOLEAN NOT NULL, + CONSTRAINT SEAT_PK PRIMARY KEY (id), + CONSTRAINT SEAT_FLIGHT_FK FOREIGN KEY (flight_id) REFERENCES flights(id) +); + +CREATE TABLE tickets ( + id NUMBER GENERATED ALWAYS AS IDENTITY, + seat_id NUMBER NOT NULL, + booking_id NUMBER NOT NULL, + CONSTRAINT TICKET_PK PRIMARY KEY (id), + CONSTRAINT TICKET_SEATS_FK FOREIGN KEY (seat_id) REFERENCES seats(id), + CONSTRAINT TICKET_BOOKINGS_FK FOREIGN KEY (booking_id) REFERENCES bookings(id) +); + +CREATE TABLE payment_methods ( + id NUMBER, + CONSTRAINT PAYMENT_METHOD_PK PRIMARY KEY (id) +); + +CREATE TABLE receipts ( + id NUMBER GENERATED ALWAYS AS IDENTITY, + created_at TIMESTAMP, + receipt_number VARCHAR(255), + total NUMBER, + booking_id NUMBER, + payment_method_id NUMBER, + CONSTRAINT RECEIPT_PK PRIMARY KEY (id), + CONSTRAINT RECEIPT_BOOKING_FK FOREIGN KEY (booking_id) REFERENCES bookings(id), + CONSTRAINT RECEIPT_PAYMENT_M_FK FOREIGN KEY (payment_method_id) REFERENCES payment_methods(id) +); \ No newline at end of file diff --git a/java/jdbc/SessionlessTransactions/src/test/resources/createUser.sql b/java/jdbc/SessionlessTransactions/src/test/resources/createUser.sql new file mode 100644 index 00000000..4df1b0d9 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/test/resources/createUser.sql @@ -0,0 +1,6 @@ +CREATE USER test_user IDENTIFIED BY test_password; +GRANT CREATE SESSION TO test_user; +GRANT CREATE TABLE TO test_user; +GRANT CREATE SEQUENCE TO test_user; +GRANT DROP ANY TABLE TO test_user; +GRANT UNLIMITED TABLESPACE TO test_user; \ No newline at end of file diff --git a/java/jdbc/SessionlessTransactions/src/test/resources/dataCleaner.sql b/java/jdbc/SessionlessTransactions/src/test/resources/dataCleaner.sql new file mode 100644 index 00000000..fc8b1f3b --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/test/resources/dataCleaner.sql @@ -0,0 +1,6 @@ +DELETE FROM receipts; +DELETE FROM payment_methods; +DELETE FROM tickets; +DELETE FROM bookings; +DELETE FROM seats; +DELETE FROM flights; \ No newline at end of file diff --git a/java/jdbc/SessionlessTransactions/src/test/resources/dataLoader.sql b/java/jdbc/SessionlessTransactions/src/test/resources/dataLoader.sql new file mode 100644 index 00000000..44c10cf3 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/test/resources/dataLoader.sql @@ -0,0 +1,9 @@ +INSERT INTO flights (id, flight_number, price) values (0, 122, 1000); +INSERT INTO flights (id, flight_number, price) values (1, 123, 2300); +INSERT INTO flights (id, flight_number, price) values (2, 124, 455); + +INSERT INTO seats (id, flight_id, available) values (0, 0, TRUE); +INSERT INTO seats (id, flight_id, available) values (1, 1, TRUE); +INSERT INTO seats (id, flight_id, available) values (2, 1, TRUE); + +INSERT INTO payment_methods (id) values (0); \ No newline at end of file diff --git a/java/jdbc/SessionlessTransactions/src/test/resources/dropSchema.sql b/java/jdbc/SessionlessTransactions/src/test/resources/dropSchema.sql new file mode 100644 index 00000000..ab96d491 --- /dev/null +++ b/java/jdbc/SessionlessTransactions/src/test/resources/dropSchema.sql @@ -0,0 +1,6 @@ +DROP TABLE receipts; +DROP TABLE payment_methods; +DROP TABLE tickets; +DROP TABLE seats; +DROP TABLE flights; +DROP TABLE bookings; \ No newline at end of file