Compare commits

...

87 Commits

Author SHA1 Message Date
Tom Hombergs
97bf254d85 update to Java 13, fixing build 2020-05-27 06:33:15 +10:00
Yavuz Tas
381fe85e39 add a sample custom validator for AppProperties and add tests 2020-05-26 08:09:11 +02:00
Yavuz Tas
a418f21ff1 add third-party component properties example and update the tests 2020-05-26 05:50:40 +02:00
Yavuz Tas
735d80d72f Code samples for Validate Spring Boot Configuration Parameters at Startup 2020-05-22 19:45:26 +02:00
Tom Hombergs
3eb97d81ec CloudFormation stacks for creating a Postgres instance, deploying a Spring Boot app and connecting the app to the database. 2020-05-11 17:07:35 +10:00
Tom Hombergs
27e1428488 update readme 2020-05-09 22:00:20 +10:00
Tom Hombergs
7b63198a08 example Docker image requiring a database connection 2020-05-09 08:38:41 +10:00
Tom Hombergs
45c947ddc1 Merge pull request #25 from arkuksin/testcontainers
Testcontainers
2020-05-07 06:47:06 +10:00
akuksin
e41458b7cd Merge branch 'master' into testcontainers 2020-05-05 20:38:44 +02:00
akuksin
03ab12c64b remove @NotNull annotation 2020-05-05 17:37:41 +02:00
akuksin
f183c92661 refactoring 2020-05-05 17:14:24 +02:00
Tom Hombergs
109dacd121 Merge pull request #24 from arkuksin/testcontainers
Testcontainers
2020-05-03 07:12:57 +10:00
Tom Hombergs
0bff861d25 Add CloudFormation templates 2020-05-02 06:39:33 +10:00
akuksin
51be8954a9 Merge branch 'master' into testcontainers 2020-04-29 20:09:38 +02:00
akuksin
4bd0201900 Merge remote-tracking branch 'upstream/master'
# Conflicts:
#	build-all.sh
2020-04-29 20:08:59 +02:00
akuksin
074b118bff add comment 2020-04-25 20:17:16 +02:00
akuksin
1a4c9421dc clean up test 2020-04-22 23:56:35 +02:00
akuksin
6168a9a47a delete unnecessary code 2020-04-22 23:11:20 +02:00
akuksin
b6cd9b8138 add testcontainers project to build-all.sh 2020-04-22 22:47:26 +02:00
prabhakar349
cc153bfd06 feat(liquibase)[]: add liquibase code example 2020-04-22 22:42:51 +02:00
thombergs
cf3a36e108 remove unnecessary package check 2020-04-22 22:42:47 +02:00
thombergs
2e6e26193f polishing 2020-04-22 22:42:47 +02:00
Nandan Bn
ae8ec346fd Package visibility changes 2020-04-22 22:42:47 +02:00
Nandan Bn
caf408a550 Fixes and added examples for transactional... 2020-04-22 22:42:47 +02:00
Nandan Bn
4543443a82 Reflect-105 Code example 2020-04-22 22:42:47 +02:00
thombergs
ae813adb18 fixed build 2020-04-22 22:42:47 +02:00
thombergs
6a27f35b2a updated with valueOf() 2020-04-22 22:42:38 +02:00
thombergs
1f9fbc2543 changed class name 2020-04-22 22:42:38 +02:00
thombergs
52f8dfc91c reduce log output in Travis Build 2020-04-22 22:42:35 +02:00
Petros Stergioulas
a5305e266f Implement UserApiDelegate, instead of the UserApi 2020-04-22 22:42:28 +02:00
Petros Stergioulas
507ff901b4 Rename module to spring-boot-openapi, also add maven wrapper in root 2020-04-22 22:42:28 +02:00
Petros Stergioulas
8b5e2ce3a4 Reflect-91 init 2020-04-22 22:42:28 +02:00
thombergs
ebd2362c1c billingmodule -> billing 2020-04-22 22:42:28 +02:00
thombergs
72f40744dd billingmodule -> billing 2020-04-22 22:42:28 +02:00
thombergs
43f9e32282 added missing Gradle files 2020-04-22 22:42:27 +02:00
thombergs
f85a0f36ae example for clean architecture boundaries with Spring Boot 2020-04-22 22:42:27 +02:00
thombergs
1f2b333a8a listContributors -> contributors 2020-04-22 22:42:25 +02:00
akuksin
4f056283b3 replace @Autowired by @Bean annotation for the authentication provider 2020-04-22 22:42:25 +02:00
akuksin
054d177ef2 remove custom code for password migration, use the feature of DelegatingPasswordEncoder instead 2020-04-22 22:42:25 +02:00
akuksin
84f441dbeb rename JdbcUserDetailPasswordService to DatabaseUserDetailPasswordService 2020-04-22 22:42:25 +02:00
akuksin
b3be06ea00 remove @Transactional annotation from RegistrationResource 2020-04-22 22:42:24 +02:00
akuksin
1fa7f545fd rename UserResources to RegistrationResource 2020-04-22 22:42:24 +02:00
akuksin
32f3e4cefa rename daoAuthenticationProvider to provider 2020-04-22 22:42:24 +02:00
akuksin
5109d98167 rename JdbcUserDetailsService to DatabaseUserDetailsService 2020-04-22 22:42:24 +02:00
akuksin
dc12f67ae4 make classes package private, apply google-java-formatter 2020-04-22 22:42:24 +02:00
akuksin
8c34f22b7e remove unnecessary comment 2020-04-22 22:42:24 +02:00
akuksin
ffa9ac6d5e simplify the configuration 2020-04-22 22:42:24 +02:00
akuksin
0b9e63571e reformat code 2020-04-22 22:42:24 +02:00
akuksin
3b69be714e add calculation of bcryt strength with Divide-and-conquer algorithm 2020-04-22 22:42:24 +02:00
akuksin
2ae9c0b2ae add simple examples for password encoders 2020-04-22 22:42:24 +02:00
akuksin
ef9fe2c506 add project spring-boot/password-encoding 2020-04-22 22:42:24 +02:00
thombergs
9653304df2 Repository -> GitRepository 2020-04-22 22:42:24 +02:00
thombergs
b7dc19cb8d argumentresolver example 2020-04-22 22:42:24 +02:00
thombergs
16bdac5240 MethodArgumentResolver example 2020-04-22 22:42:20 +02:00
thombergs
53a7cd866f example of converters in Spring Data JDBC 2020-04-22 22:42:20 +02:00
Petromir Dzhunev
78fd7fb89f REFLECT-2 Add missing bits
Add undo migration, add Jenkins files, add Java-base migration
2020-04-22 22:41:47 +02:00
Petromir Dzhunev
94ebb0bde7 REFLECT-2 Remove redundant parameters to Gradle plugin 2020-04-22 22:41:47 +02:00
Petromir Dzhunev
681d34b775 REFLECT-2 Integrating H2 with Flyway 2020-04-22 22:41:47 +02:00
Petromir Dzhunev
d8ca7d3f04 Initial set-up of Flyway data migration project 2020-04-22 22:41:47 +02:00
thombergs
bf68901255 changes after review 2020-04-22 22:41:47 +02:00
Mukul Sharma
ab949c2a0b Reflect-76 ISP explained added code examples 2020-04-22 22:41:47 +02:00
thombergs
90f7b1ad39 refactored and made the tests work 2020-04-22 22:41:47 +02:00
Vasudha Venkatesan
4b959cb797 Code example for field injection and other changes 2020-04-22 22:41:47 +02:00
Vasudha Venkatesan
670c4c845a Code example changes 2020-04-22 22:41:47 +02:00
Vasudha Venkatesan
531d05de64 Code format changes and Added maven wrapper 2020-04-22 22:41:47 +02:00
Vasudha Venkatesan
3a929134ba Constructor and setter based Dependency injection code example 2020-04-22 22:41:47 +02:00
akuksin
f1aa143b96 initial commit 2020-04-22 21:11:39 +02:00
Tom Hombergs
f05e8f75cd Merge pull request #23 from pdigumarthi/feat-liquibase-example
feat(liquibase)[]: add liquibase code example
2020-04-14 08:33:29 +10:00
prabhakar349
190f2511af feat(liquibase)[]: add liquibase code example 2020-04-12 18:24:09 -04:00
thombergs
4ecc79977a remove unnecessary package check 2020-04-09 06:37:44 +10:00
Tom Hombergs
7e4b6d4cac Merge pull request #17 from Lucaarioo/master
Reflect-105 Code example
2020-03-31 06:10:11 +11:00
thombergs
e023c7c42f polishing 2020-03-31 06:06:50 +11:00
Tom Hombergs
a857ad5455 Merge pull request #18 from vasudhavenkatesan/master
REFLECT -104 : Constructor and setter based Dependency injection code example
2020-03-29 06:46:03 +11:00
thombergs
51460691bb fixed build 2020-03-28 06:34:57 +11:00
thombergs
01b0ef3eb8 Merge branch 'master' into vasudha-master 2020-03-28 06:29:07 +11:00
thombergs
4f006376c1 updated with valueOf() 2020-03-25 06:55:19 +11:00
thombergs
01db1e9805 refactored and made the tests work 2020-03-25 06:06:04 +11:00
thombergs
a989129816 changed class name 2020-03-14 08:58:44 +11:00
thombergs
4ceee9a453 reduce log output in Travis Build 2020-03-12 06:49:07 +11:00
Tom Hombergs
c90578311b Merge pull request #21 from thombergs/openapi
Openapi
2020-03-11 07:05:34 +11:00
Vasudha Venkatesan
ca32144e80 Code example for field injection and other changes 2020-03-10 10:55:35 +05:30
Vasudha Venkatesan
1f4484f25f Code example changes 2020-02-26 14:43:33 +05:30
Nandan Bn
47baf5ea91 Package visibility changes 2020-02-25 01:08:13 +05:30
Vasudha Venkatesan
61ed2eaaa8 Code format changes and Added maven wrapper 2020-02-24 12:44:47 +05:30
Nandan Bn
c4ba025b17 Fixes and added examples for transactional... 2020-02-22 10:22:16 +05:30
Vasudha Venkatesan
1e9c30733d Constructor and setter based Dependency injection code example 2020-02-17 22:46:46 +05:30
Nandan Bn
90657e3a6b Reflect-105 Code example 2020-02-17 12:02:27 +05:30
135 changed files with 5594 additions and 42 deletions

32
aws/aws-rds-hello-world/.gitignore vendored Normal file
View File

@@ -0,0 +1,32 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**
!**/src/test/**
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/

View File

@@ -0,0 +1,5 @@
FROM openjdk:8-jdk-alpine
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
EXPOSE 8080

View File

@@ -0,0 +1,25 @@
# RDS Hello World Application
This is a simple Spring Boot application which requires access to a PostgreSQL database.
The application has a single endpoint `/hello` which prints out if the database connection was successful.
Get it in a Docker image via `docker pull reflectoring/aws-rds-hello-world`.
Use the image instead of your real application to test AWS CloudFormation stacks which need access to a database.
## Testing AWS RDS connectivity with this application
1. Create an RDS PostgreSQL database with the AWS console.
2. Note the endpoint of your RDS database in the AWS console.
3. Deploy the Docker container `reflectoring/aws-rds-hello-world` into AWS instead of your real application (this could be via a CloudFormation stack, manually, or however you are deploying your app).
4. Configure your deployment in a way that Docker will pass the coordinates to your RDS database as environment variables, equivalent to this command:
```
docker run \
-e SPRING_DATASOURCE_URL=':'<RDS-ENDPOINT>:5432/postgres \
-e SPRING_DATASOURCE_USERNAME=<USERNAME> \
-e SPRING_DATASOURCE_PASSWORD=<PASSWORD> \
-p 8080:8080 reflectoring/aws-rds-hello-world
```
5. If the Spring Boot application can connect to the database, it will start up sucessfully and serve a message on the endpoint `/hello`.

View File

@@ -0,0 +1,31 @@
plugins {
id 'org.springframework.boot' version '2.2.4.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'io.reflectoring'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
// database
implementation 'org.flywaydb:flyway-core'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
runtimeOnly 'org.postgresql:postgresql'
testRuntimeOnly 'org.postgresql:postgresql'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}

View File

@@ -0,0 +1,18 @@
version: '3.7'
services:
postgres:
container_name: "rds-hello-world"
image: postgres
volumes:
- rds-hello-world:/var/lib/postgresql/data
ports:
- 5432:5432
environment:
- POSTGRES_USER=hello
- POSTGRES_PASSWORD=hello
volumes:
rds-hello-world:
driver: local

Binary file not shown.

View File

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

172
aws/aws-rds-hello-world/gradlew vendored Executable file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
aws/aws-rds-hello-world/gradlew.bat vendored Executable file
View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1 @@
rootProject.name = 'aws-rds-hello-world'

View File

@@ -0,0 +1,13 @@
package io.reflectoring.awshelloworld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AwsHelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(AwsHelloWorldApplication.class, args);
}
}

View File

@@ -0,0 +1,23 @@
package io.reflectoring.awshelloworld;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
class HelloWorldController {
private final UserRepository userRepository;
HelloWorldController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping("/hello")
String helloWorld(){
Iterable<User> users = userRepository.findAll();
return "Hello AWS! Successfully connected to the database!";
}
}

View File

@@ -0,0 +1,38 @@
package io.reflectoring.awshelloworld;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
@Table("hello_user")
public class User {
@Id
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
public User() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,6 @@
package io.reflectoring.awshelloworld;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, Long> {
}

View File

@@ -0,0 +1,5 @@
spring:
datasource:
url: jdbc:postgresql://localhost:5432/hello
username: hello
password: hello

View File

@@ -0,0 +1,5 @@
create table hello_user (
id varchar(36) not null unique,
name varchar(100) not null,
primary key(id)
);

View File

@@ -0,0 +1,8 @@
# Overview
![ECS in two public subnets](ecs-in-two-public-subnets.svg)
# Companion Blog Post
[The AWS Journey Part 2: Deploying a Docker image from the Command Line with CloudFormation](https://reflectoring.io/aws-cloudformation-deploy-docker-image/)

View File

@@ -0,0 +1 @@
<mxfile modified="2020-04-30T21:28:18.347Z" host="app.diagrams.net" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36" etag="X9Eef2gSXNvxkCnmY-_0" version="13.0.4" type="device"><diagram id="Ht1M8jgEwFfnCIfOTk4-" name="Page-1">7Vpbb+o4EP41PC7K/fLIrWcr9UiVWO05+4RM4garIc4aU6C/fseJc7ND6ekB9nQXVKmZsTMez3yf7TEM7Ml6/4WhfPWVxjgdWEa8H9jTgWWZphXCP6E5lBrPcEpFwkgsOzWKOXnFUmlI7ZbEeNPpyClNOcm7yohmGY54R4cYo7tutyeadkfNUYI1xTxCqa79RmK+qublhU3D75gkKzl0YPllwxpVneVMNisU011LZc8G9oRRysun9X6CUxG8Ki7le3dHWmvHGM74e15Y4cjav97736avy2fP+Rr/vVj+Vjn3gtKtnPGfjxPpMD9UUcgpyXgRSXcMf/DSxBi40DIR0tByFYUq+12FqUvCRlehyn5XYarmTWV8U3WwpdCkjnlDGd9oOQh/9phueUoyPKkxZ4AyYSgmkIsJTSkDXUYziN54xdcpSCY87laE43mOIhHVHfAFdE804xL1plXJMvDiHUBNLp7X+0QQbIh2G2eYMLrNiyHvAfe9rYuXPBKvc0afceXSwLItJwhMRwxE0lRx9QUzTgD6o5QkwiqnYhAkpRQ/cWER/CdZ8lBIU9uQPreGGI3G/jgAfYw2KxzL8EicwRB4fxTBZs0LWFAwXWPODtClesGTaJVrSQXeXUNMO5S6VYuTjiGVSC4GSW264Qs8SMr8AH3MUKPP43aZkkjQZ7vMML9R6fNTaYOjLSP8sGg6zwteSXffRzLQz8I7e+adj2n1OOdmmm0pTLPCoR1obKsZ2Gab6ZkFSC5COPvGtxvf/vt8c4Buftj6uL8A90xfI99sMgfFHLMXAshQqddzxNBS5468SeC1Q2oezZcKMCU7talzJMTvJsTvXf9MuycHVnip04arxX9geamA7hIeEvEwynNYDREnwK2qjVWNDxTFuhZogLIIs6oFXKvNaSntXT46bKhS94CWOH2kG1L4Yk+XlHO6PsnFCBIEvnQWn56FxB6iZqaLFCa2WFbz0JaBOzdwbef4OncGvMAZs4MX0w+GgQ6Yqmps48W3LgSX4DRc7kWwi13TQ2uRhmy5yYuIqCj5gjjeocMnAwmR81sk0v1+bHgXxUa9SrSw4esL+lWxodct5VL+B9o8vy+f6tZoW3difC2OIsZ+MDPa/JsSBobKlGeUiQioeZka7gQ2nJ4d46n4XAFRp843aJOX4Xgie+FH/4GH4Q3dsgiXx50xiH0HHxxtzgO2utatwGb07lx+z8blX6xKdm5o+3+gzXL/fbRZeo2oYQzHCa6CKxBBE5qhdNZo25nBWTwSN8kivymNnoUqXRZyleYCOYjxqp/cPODNO5LWO5R29nXcsSeQCmVOFtfggtizw/e28JcQoKqT4nTfbpwe2tIjZgTiKNDVKk7EfN9OMYSnAM5bgZWbBkw0wfzUwUPHTPvgXOGG4RSOcC9d5/ogIc09imq+wV+owM9Uap9yTvKl9t24YsdU6i9bvTEs56wZKgBaT/EnMOvdMPsRzJ6Eons1KFrqUvhhLAYKFs0rY1Ev8z8ZFveEf6/GhucWEkFqgCiEQ0u44NLpvhOu5UnpKng1TfXKyfgYXq1Tho7gFXIt6sm6m7yoPeqw7fYT7JhfWv/QUehSenBe8vQV3TfyXIU84dW4Y1ff+9VfjzjDIDDqj2LwvUxyHLdr1hOn6aNmfxFemc41eKVfWLx1mfUp76isH7+dqn9T0nwtUv1KxT7TxaaS7MDvu7vyw6FUtks81zlOrCMlHojNL2JK8DS/K7Jn/wA=</diagram></mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,247 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: A network stack for deploying containers in AWS ECS.
This stack creates a VPC with two public subnets and a loadbalancer to balance traffic between those subnets.
Derived from a template at https://github.com/nathanpeck/aws-cloudformation-fargate.
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: '10.0.0.0/16'
PublicSubnetOne:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: {Ref: 'AWS::Region'}
VpcId: !Ref 'VPC'
CidrBlock: '10.0.1.0/24'
MapPublicIpOnLaunch: true
PublicSubnetTwo:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: {Ref: 'AWS::Region'}
VpcId: !Ref 'VPC'
CidrBlock: '10.0.2.0/24'
MapPublicIpOnLaunch: true
InternetGateway:
Type: AWS::EC2::InternetGateway
GatewayAttachement:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref 'VPC'
InternetGatewayId: !Ref 'InternetGateway'
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref 'VPC'
PublicSubnetOneRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetOne
RouteTableId: !Ref PublicRouteTable
PublicSubnetTwoRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetTwo
RouteTableId: !Ref PublicRouteTable
PublicRoute:
Type: AWS::EC2::Route
DependsOn: GatewayAttachement
Properties:
RouteTableId: !Ref 'PublicRouteTable'
DestinationCidrBlock: '0.0.0.0/0'
GatewayId: !Ref 'InternetGateway'
PublicLoadBalancerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the public facing load balancer
VpcId: !Ref 'VPC'
SecurityGroupIngress:
# Allow access to ALB from anywhere on the internet
- CidrIp: 0.0.0.0/0
IpProtocol: -1
PublicLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
Subnets:
# The load balancer is placed into the public subnets, so that traffic
# from the internet can reach the load balancer directly via the internet gateway
- !Ref PublicSubnetOne
- !Ref PublicSubnetTwo
SecurityGroups: [!Ref 'PublicLoadBalancerSecurityGroup']
DummyTargetGroupPublic:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 6
HealthCheckPath: /
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
Name: "no-op"
Port: 80
Protocol: HTTP
UnhealthyThresholdCount: 2
VpcId: !Ref 'VPC'
PublicLoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
DependsOn:
- PublicLoadBalancer
Properties:
DefaultActions:
- TargetGroupArn: !Ref 'DummyTargetGroupPublic'
Type: 'forward'
LoadBalancerArn: !Ref 'PublicLoadBalancer'
Port: 80
Protocol: HTTP
ECSCluster:
Type: AWS::ECS::Cluster
ECSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the ECS containers
VpcId: !Ref 'VPC'
ECSSecurityGroupIngressFromPublicALB:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Ingress from the public ALB
GroupId: !Ref 'ECSSecurityGroup'
IpProtocol: -1
SourceSecurityGroupId: !Ref 'PublicLoadBalancerSecurityGroup'
ECSSecurityGroupIngressFromSelf:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Ingress from other containers in the same security group
GroupId: !Ref 'ECSSecurityGroup'
IpProtocol: -1
SourceSecurityGroupId: !Ref 'ECSSecurityGroup'
ECSRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
Policies:
- PolicyName: ecs-service
PolicyDocument:
Statement:
- Effect: Allow
Action:
# Rules which allow ECS to attach network interfaces to instances
# on your behalf in order for awsvpc networking mode to work right
- 'ec2:AttachNetworkInterface'
- 'ec2:CreateNetworkInterface'
- 'ec2:CreateNetworkInterfacePermission'
- 'ec2:DeleteNetworkInterface'
- 'ec2:DeleteNetworkInterfacePermission'
- 'ec2:Describe*'
- 'ec2:DetachNetworkInterface'
# Rules which allow ECS to update load balancers on your behalf
# with the information sabout how to send traffic to your containers
- 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer'
- 'elasticloadbalancing:DeregisterTargets'
- 'elasticloadbalancing:Describe*'
- 'elasticloadbalancing:RegisterInstancesWithLoadBalancer'
- 'elasticloadbalancing:RegisterTargets'
Resource: '*'
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs-tasks.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
Policies:
- PolicyName: AmazonECSTaskExecutionRolePolicy
PolicyDocument:
Statement:
- Effect: Allow
Action:
# Allow the ECS Tasks to download images from ECR
- 'ecr:GetAuthorizationToken'
- 'ecr:BatchCheckLayerAvailability'
- 'ecr:GetDownloadUrlForLayer'
- 'ecr:BatchGetImage'
# Allow the ECS tasks to upload logs to CloudWatch
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: '*'
Outputs:
ClusterName:
Description: The name of the ECS cluster
Value: !Ref 'ECSCluster'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ClusterName' ] ]
ExternalUrl:
Description: The url of the external load balancer
Value: !Join ['', ['http://', !GetAtt 'PublicLoadBalancer.DNSName']]
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ExternalUrl' ] ]
ECSRole:
Description: The ARN of the ECS role
Value: !GetAtt 'ECSRole.Arn'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSRole' ] ]
ECSTaskExecutionRole:
Description: The ARN of the ECS role
Value: !GetAtt 'ECSTaskExecutionRole.Arn'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSTaskExecutionRole' ] ]
PublicListener:
Description: The ARN of the public load balancer's Listener
Value: !Ref PublicLoadBalancerListener
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicListener' ] ]
VPCId:
Description: The ID of the VPC that this stack is deployed in
Value: !Ref 'VPC'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'VPCId' ] ]
PublicSubnetOne:
Description: Public subnet one
Value: !Ref 'PublicSubnetOne'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ]
PublicSubnetTwo:
Description: Public subnet two
Value: !Ref 'PublicSubnetTwo'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwo' ] ]
ECSSecurityGroup:
Description: A security group used to allow ECS containers to receive traffic
Value: !Ref 'ECSSecurityGroup'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSSecurityGroup' ] ]

View File

@@ -0,0 +1,139 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: Deploy a service on AWS Fargate, hosted in two public subnets and accessible via a public load balancer.
Derived from a template at https://github.com/nathanpeck/aws-cloudformation-fargate.
Parameters:
StackName:
Type: String
Description: The name of the networking stack that
these resources are put into.
ServiceName:
Type: String
Description: A human-readable name for the service.
HealthCheckPath:
Type: String
Default: /health
Description: Path to perform the healthcheck on each instance.
HealthCheckIntervalSeconds:
Type: Number
Default: 5
Description: Number of seconds to wait between each health check.
ImageUrl:
Type: String
Description: The url of a docker image that will handle incoming traffic.
ContainerPort:
Type: Number
Default: 80
Description: The port number the application inside the docker container
is binding to.
ContainerCpu:
Type: Number
Default: 256
Description: How much CPU to give the container. 1024 is 1 CPU.
ContainerMemory:
Type: Number
Default: 512
Description: How much memory in megabytes to give the container.
Path:
Type: String
Default: "*"
Description: A path on the public load balancer that this service
should be connected to.
DesiredCount:
Type: Number
Default: 2
Description: How many copies of the service task to run.
Resources:
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: !Ref 'HealthCheckIntervalSeconds'
HealthCheckPath: !Ref 'HealthCheckPath'
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
TargetType: ip
Name: !Ref 'ServiceName'
Port: !Ref 'ContainerPort'
Protocol: HTTP
UnhealthyThresholdCount: 2
VpcId:
Fn::ImportValue:
!Join [':', [!Ref 'StackName', 'VPCId']]
LoadBalancerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- TargetGroupArn: !Ref 'TargetGroup'
Type: 'forward'
Conditions:
- Field: path-pattern
Values: [!Ref 'Path']
ListenerArn:
Fn::ImportValue:
!Join [':', [!Ref 'StackName', 'PublicListener']]
Priority: 1
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Ref 'ServiceName'
RetentionInDays: 1
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Ref 'ServiceName'
Cpu: !Ref 'ContainerCpu'
Memory: !Ref 'ContainerMemory'
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ExecutionRoleArn:
Fn::ImportValue:
!Join [':', [!Ref 'StackName', 'ECSTaskExecutionRole']]
ContainerDefinitions:
- Name: !Ref 'ServiceName'
Cpu: !Ref 'ContainerCpu'
Memory: !Ref 'ContainerMemory'
Image: !Ref 'ImageUrl'
PortMappings:
- ContainerPort: !Ref 'ContainerPort'
LogConfiguration:
LogDriver: 'awslogs'
Options:
awslogs-group: !Ref 'ServiceName'
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: !Ref 'ServiceName'
Service:
Type: AWS::ECS::Service
DependsOn: LoadBalancerRule
Properties:
ServiceName: !Ref 'ServiceName'
Cluster:
Fn::ImportValue:
!Join [':', [!Ref 'StackName', 'ClusterName']]
LaunchType: FARGATE
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 50
DesiredCount: !Ref 'DesiredCount'
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- Fn::ImportValue:
!Join [':', [!Ref 'StackName', 'ECSSecurityGroup']]
Subnets:
- Fn::ImportValue:
!Join [':', [!Ref 'StackName', 'PublicSubnetOne']]
- Fn::ImportValue:
!Join [':', [!Ref 'StackName', 'PublicSubnetTwo']]
TaskDefinition: !Ref 'TaskDefinition'
LoadBalancers:
- ContainerName: !Ref 'ServiceName'
ContainerPort: !Ref 'ContainerPort'
TargetGroupArn: !Ref 'TargetGroup'

View File

@@ -0,0 +1,7 @@
# Overview
![RDS in private subnet](rds-in-private-subnet.svg)
# Companion Blog Post
TO DO

View File

@@ -0,0 +1,88 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: A stack that creates an RDS instance and places it into two subnets
Parameters:
NetworkStackName:
Type: String
Description: The name of the networking stack that this stack will build upon.
DBInstanceClass:
Type: String
Description: The ID of the second subnet to place the RDS instance into.
Default: 'db.t2.micro'
DBName:
Type: String
Description: The name of the database that is created within the PostgreSQL instance.
DBUsername:
Type: String
Description: The master user name for the PostgreSQL instance.
Resources:
Secret:
Type: "AWS::SecretsManager::Secret"
Properties:
Name: !Ref 'DBUsername'
GenerateSecretString:
# This will generate a JSON object with the keys "username" and password.
SecretStringTemplate: !Join ['', ['{"username": "', !Ref 'DBUsername' ,'"}']]
GenerateStringKey: "password"
PasswordLength: 32
ExcludeCharacters: '"@/\'
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnet group for the RDS instance
DBSubnetGroupName: DBSubnetGroup
SubnetIds:
- Fn::ImportValue:
!Join [':', [!Ref 'NetworkStackName', 'PrivateSubnetOne']]
- Fn::ImportValue:
!Join [':', [!Ref 'NetworkStackName', 'PrivateSubnetTwo']]
PostgresInstance:
Type: AWS::RDS::DBInstance
Properties:
AllocatedStorage: 20
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: {Ref: 'AWS::Region'}
DBInstanceClass: !Ref 'DBInstanceClass'
DBName: !Ref 'DBName'
DBSubnetGroupName: !Ref 'DBSubnetGroup'
Engine: postgres
EngineVersion: 11.5
MasterUsername: !Ref 'DBUsername'
MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref Secret, ':SecretString:password}}' ]]
PubliclyAccessible: false
VPCSecurityGroups:
- Fn::ImportValue:
!Join [':', [!Ref 'NetworkStackName', 'DBSecurityGroupId']]
SecretRDSInstanceAttachment:
Type: "AWS::SecretsManager::SecretTargetAttachment"
Properties:
SecretId: !Ref Secret
TargetId: !Ref PostgresInstance
TargetType: AWS::RDS::DBInstance
Outputs:
EndpointAddress:
Description: Address of the RDS endpoint.
Value: !GetAtt 'PostgresInstance.Endpoint.Address'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'EndpointAddress' ] ]
EndpointPort:
Description: Port of the RDS endpoint.
Value: !GetAtt 'PostgresInstance.Endpoint.Port'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'EndpointPort' ] ]
DBName:
Description: The name of the database that is created within the PostgreSQL instance.
Value: !Ref DBName
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'DBName' ] ]
Secret:
Description: Reference to the secret containing the password to the database.
Value: !Ref 'Secret'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'Secret' ] ]

View File

@@ -0,0 +1,298 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: A network stack for deploying containers in AWS ECS.
This stack creates a VPC with two public subnets and a loadbalancer to balance traffic between those subnets.
Derived from a template at https://github.com/nathanpeck/aws-cloudformation-fargate.
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: '10.0.0.0/16'
PublicSubnetOne:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: {Ref: 'AWS::Region'}
VpcId: !Ref 'VPC'
CidrBlock: '10.0.1.0/24'
MapPublicIpOnLaunch: true
PublicSubnetTwo:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: {Ref: 'AWS::Region'}
VpcId: !Ref 'VPC'
CidrBlock: '10.0.2.0/24'
MapPublicIpOnLaunch: true
InternetGateway:
Type: AWS::EC2::InternetGateway
GatewayAttachement:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref 'VPC'
InternetGatewayId: !Ref 'InternetGateway'
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref 'VPC'
PublicSubnetOneRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetOne
RouteTableId: !Ref PublicRouteTable
PublicSubnetTwoRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetTwo
RouteTableId: !Ref PublicRouteTable
PublicRoute:
Type: AWS::EC2::Route
DependsOn: GatewayAttachement
Properties:
RouteTableId: !Ref 'PublicRouteTable'
DestinationCidrBlock: '0.0.0.0/0'
GatewayId: !Ref 'InternetGateway'
PublicLoadBalancerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the public facing load balancer
VpcId: !Ref 'VPC'
SecurityGroupIngress:
# Allow access to ALB from anywhere on the internet
- CidrIp: 0.0.0.0/0
IpProtocol: -1
PublicLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
Subnets:
# The load balancer is placed into the public subnets, so that traffic
# from the internet can reach the load balancer directly via the internet gateway
- !Ref PublicSubnetOne
- !Ref PublicSubnetTwo
SecurityGroups: [!Ref 'PublicLoadBalancerSecurityGroup']
DummyTargetGroupPublic:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 6
HealthCheckPath: /
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
Name: "no-op"
Port: 80
Protocol: HTTP
UnhealthyThresholdCount: 2
VpcId: !Ref 'VPC'
PublicLoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
DependsOn:
- PublicLoadBalancer
Properties:
DefaultActions:
- TargetGroupArn: !Ref 'DummyTargetGroupPublic'
Type: 'forward'
LoadBalancerArn: !Ref 'PublicLoadBalancer'
Port: 80
Protocol: HTTP
ECSCluster:
Type: AWS::ECS::Cluster
ECSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the ECS containers
VpcId: !Ref 'VPC'
ECSSecurityGroupIngressFromPublicALB:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Ingress from the public ALB
GroupId: !Ref 'ECSSecurityGroup'
IpProtocol: -1
SourceSecurityGroupId: !Ref 'PublicLoadBalancerSecurityGroup'
ECSSecurityGroupIngressFromSelf:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Ingress from other containers in the same security group
GroupId: !Ref 'ECSSecurityGroup'
IpProtocol: -1
SourceSecurityGroupId: !Ref 'ECSSecurityGroup'
ECSRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
Policies:
- PolicyName: ecs-service
PolicyDocument:
Statement:
- Effect: Allow
Action:
# Rules which allow ECS to attach network interfaces to instances
# on your behalf in order for awsvpc networking mode to work right
- 'ec2:AttachNetworkInterface'
- 'ec2:CreateNetworkInterface'
- 'ec2:CreateNetworkInterfacePermission'
- 'ec2:DeleteNetworkInterface'
- 'ec2:DeleteNetworkInterfacePermission'
- 'ec2:Describe*'
- 'ec2:DetachNetworkInterface'
# Rules which allow ECS to update load balancers on your behalf
# with the information sabout how to send traffic to your containers
- 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer'
- 'elasticloadbalancing:DeregisterTargets'
- 'elasticloadbalancing:Describe*'
- 'elasticloadbalancing:RegisterInstancesWithLoadBalancer'
- 'elasticloadbalancing:RegisterTargets'
Resource: '*'
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs-tasks.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
Policies:
- PolicyName: AmazonECSTaskExecutionRolePolicy
PolicyDocument:
Statement:
- Effect: Allow
Action:
# Allow the ECS Tasks to download images from ECR
- 'ecr:GetAuthorizationToken'
- 'ecr:BatchCheckLayerAvailability'
- 'ecr:GetDownloadUrlForLayer'
- 'ecr:BatchGetImage'
# Allow the ECS tasks to upload logs to CloudWatch
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: '*'
PrivateSubnetOne:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: {Ref: 'AWS::Region'}
VpcId: !Ref 'VPC'
CidrBlock: '10.0.101.0/24'
MapPublicIpOnLaunch: false
PrivateSubnetTwo:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: {Ref: 'AWS::Region'}
VpcId: !Ref 'VPC'
CidrBlock: '10.0.102.0/24'
MapPublicIpOnLaunch: false
DBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the RDS instance
VpcId: !Ref 'VPC'
DBSecurityGroupIngressFromECS:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Ingress from the ECS containers to the RDS instance
GroupId: !Ref 'DBSecurityGroup'
IpProtocol: -1
SourceSecurityGroupId: !Ref 'ECSSecurityGroup'
Outputs:
PrivateSubnetOne:
Description: Private subnet one
Value: !Ref 'PrivateSubnetOne'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PrivateSubnetOne' ] ]
PrivateSubnetTwo:
Description: Private subnet two
Value: !Ref 'PrivateSubnetTwo'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PrivateSubnetTwo' ] ]
DBSecurityGroupId:
Description: ID of the security group that an RDS instance can be placed into.
Value: !Ref 'DBSecurityGroup'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'DBSecurityGroupId' ] ]
ClusterName:
Description: The name of the ECS cluster
Value: !Ref 'ECSCluster'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ClusterName' ] ]
ExternalUrl:
Description: The url of the external load balancer
Value: !Join ['', ['http://', !GetAtt 'PublicLoadBalancer.DNSName']]
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ExternalUrl' ] ]
ECSRole:
Description: The ARN of the ECS role
Value: !GetAtt 'ECSRole.Arn'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSRole' ] ]
ECSTaskExecutionRole:
Description: The ARN of the ECS role
Value: !GetAtt 'ECSTaskExecutionRole.Arn'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSTaskExecutionRole' ] ]
PublicListener:
Description: The ARN of the public load balancer's Listener
Value: !Ref PublicLoadBalancerListener
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicListener' ] ]
VPCId:
Description: The ID of the VPC that this stack is deployed in
Value: !Ref 'VPC'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'VPCId' ] ]
PublicSubnetOne:
Description: Public subnet one
Value: !Ref 'PublicSubnetOne'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ]
PublicSubnetTwo:
Description: Public subnet two
Value: !Ref 'PublicSubnetTwo'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwo' ] ]
ECSSecurityGroup:
Description: A security group used to allow ECS containers to receive traffic
Value: !Ref 'ECSSecurityGroup'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSSecurityGroup' ] ]

View File

@@ -0,0 +1 @@
<mxfile modified="2020-05-11T21:18:00.281Z" host="app.diagrams.net" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36" etag="d22WT4Cb11cg7E6o-Mdr" version="13.0.9" type="device"><diagram id="Ht1M8jgEwFfnCIfOTk4-" name="Page-1">7VvZcuI4FP0aHpuyJK+PYUk6VemaTNHTyxNlsGI8MRYtiy1fP5KRwZbEkkBI00OKqljXspZ7z7mLDA3UHi/uaDgZfSERThvQihYN1GlACJDj8H9CslxJPM9eCWKaRLLTRtBLXrAUWlI6TSKc1zoyQlKWTOrCIckyPGQ1WUgpmde7PZG0PuskjLEm6A3DVJd+TyI2klLgBpsbn3ESj+TUPvRWN8Zh2VnuJB+FEZlXRKjbQG1KCFtdjRdtnArllXpZPXe75e56YRRn7JAHHuJp6+UXX9i3PB/cv7TDr5/+/SRHmYXpVG74cTpIk6FY73SQYSbXzpalQiYkyVihVKfFP3zOttVw+J22aDWhowjUtlcXAL0lxqgL1LZXFwB1eKDMD9QFVgRaqza8pcxvVRbIP6hFpixNMtxew8/iwpiGUcLN0iYpoVyWkYxrrzVi45S3AL+cjxKGe5NwKLQ659ThsieSMUkAAMu2VLx4hgNoIq7Hi1hwrRnOc7sZUzKdFFPecwoY7/ZzPJzShC37m849RskzlsvNi0a52AZE0PZ9YIslJGlakXeDW9R1uXyGKUs4SW7SJBaTMiKGDWUrxU9MDMu3l2TxQ9HqIEtuyTRPFOYjHMnlSEjyKfBiK9bBmkHc9WAyxowueRf5AHIk6aTXQaU7mVc47EvZqEJf4IICIYX7kK4jXg+/YRe/kAQzk22Eh3Dxcu9977wMnl37S/SrP/hUeoIK2749tq8Mu3yGzSbDg3kkl3oiCt3ctLyWb6aQxhcDq7ZSCLh1CgFLp5BrGyiEAut4/hiDla0HK5rMQoav0eoP4tL/L1o5wQdHq0U6ve3jv2af7eXPX2zwzwu+m5pSwyvbrmz7Hdh2VGBT2QbgmdlmjG2OxrYO36zVk0Sz7grzqIQz5BWawZwbt+27VUWCrVZSYaXYZD2UYs0T+EAXKlZxDFYptV9LN8AJ0g1juo6utfHV//15/k+tjc/u/4xkA57Gtm67JzwgprOEQ+G3dn3HGcQ/wPUhU6X1bq5Pj0YN6KZivwN+EYuLm8mE+8KQJZxZ5T1a3nwgYaRLue7CbIhpeYcvbT2cZl+j86hxobTjQzjA6SPJk2ItqDMgjJHxXiYOucX4Wmqux+BGUDPc7LSf8o31B+U+NCdw6/gOsrd7uROgBdp1tEDXagKkl+aBjhcPvhNc/P1wuRfKLmKmG46FGbJBPik0oqLkjpcY83B5YSBJ5P76sVy+GRvuu2Jj7SU22PhYYARbnPrXMH8+zJhqVETwVsyvKVEo2PO7VpV8nYTygVb2zggVGlCN0rGcNg89htjxVPydAU77Upswn6zU8ZQsxDrMuQ7FOZnSIV5lOi3eNOU8eJifBmk2UGKWryPNM4Qs770iFtSzdQ1gOIpxqVkBBxKTLEy7G2nVLDiLbsT7Q2HclAyfhSgdFO3SxgVsQsrKfpLI/MnbJF17Cy0psZ2WK2DKE84sWiOL24Iuf1QbP0WD59ey2VlUb3aW1dYjpgnXo4DW7jRxBZJdepSU5fuKMdvv84VOdwKmmsSUR8gUpzyczuqvWE2QkMM9irpqAz6lgIeBq+Wlq33K56ovRZWhgFJ2Il/PcVeq0MYqYLre6BHIda/IPQC5ewHpfBwgoXVCRCrVAArOj0hTPndJiHw7iIKPAxGyAhVEb0OQXX7npkSQbTWtyh84CEzcECIJX3eTZ1vblw81DuxcpdYfOAqWVys4LbL1hHRXpXKRBQh8femx/mrS5sSr/LITOlHVqhYmgZ4uBk3D8a5jH58wGl9vwf1I4NZiMcW9vx94x/uM+6dMHERdFCxolOt4gF0PFV8aeL9S1LXtmsWBZ6/DWNXoUDe5v8N1HmXySy8Rtpplb1ZvSOp3keJDwh9Qwh+0m77/tgjoAnNsOXXMc3bHsNf2PzrmGd8s6qi/nsH8LmcwR70yVc9gkGs3TXH1nY5hjGC7+Fp2kbAf5dz8ulLJ8tamkBWNZaVhLmMVP/o23+3ovns7zz/EVSPrrZWK6g8PLHTPXZvY1hn8tP4C9MKoc/pjoNfyZ1f8Oyz3OQt/7KBekEOVBE0L2bYPPcd3oeW9MQdybGVYHh38wHUc4PHQgdR3PqdKidz6bhTq7O2/L4VS64ojUyje3PwCZ9V98zsm1P0P</diagram></mxfile>

View File

@@ -0,0 +1,164 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: Deploy a service on AWS Fargate, hosted in two public subnets and accessible via a public load balancer.
Derived from a template at https://github.com/nathanpeck/aws-cloudformation-fargate.
Parameters:
NetworkStackName:
Type: String
Description: The name of the networking stack that
these resources are put into.
DatabaseStackName:
Type: String
Description: The name of the database stack with the database this service should connect to.
ServiceName:
Type: String
Description: A human-readable name for the service.
HealthCheckPath:
Type: String
Default: /health
Description: Path to perform the healthcheck on each instance.
HealthCheckIntervalSeconds:
Type: Number
Default: 5
Description: Number of seconds to wait between each health check.
ImageUrl:
Type: String
Description: The url of a docker image that will handle incoming traffic.
ContainerPort:
Type: Number
Default: 80
Description: The port number the application inside the docker container
is binding to.
ContainerCpu:
Type: Number
Default: 256
Description: How much CPU to give the container. 1024 is 1 CPU.
ContainerMemory:
Type: Number
Default: 512
Description: How much memory in megabytes to give the container.
Path:
Type: String
Default: "*"
Description: A path on the public load balancer that this service
should be connected to.
DesiredCount:
Type: Number
Default: 2
Description: How many copies of the service task to run.
Resources:
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: !Ref 'HealthCheckIntervalSeconds'
HealthCheckPath: !Ref 'HealthCheckPath'
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
TargetType: ip
Name: !Ref 'ServiceName'
Port: !Ref 'ContainerPort'
Protocol: HTTP
UnhealthyThresholdCount: 2
VpcId:
Fn::ImportValue:
!Join [':', [!Ref 'NetworkStackName', 'VPCId']]
LoadBalancerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- TargetGroupArn: !Ref 'TargetGroup'
Type: 'forward'
Conditions:
- Field: path-pattern
Values: [!Ref 'Path']
ListenerArn:
Fn::ImportValue:
!Join [':', [!Ref 'NetworkStackName', 'PublicListener']]
Priority: 1
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Ref 'ServiceName'
RetentionInDays: 1
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Ref 'ServiceName'
Cpu: !Ref 'ContainerCpu'
Memory: !Ref 'ContainerMemory'
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ExecutionRoleArn:
Fn::ImportValue:
!Join [':', [!Ref 'NetworkStackName', 'ECSTaskExecutionRole']]
ContainerDefinitions:
- Name: !Ref 'ServiceName'
Cpu: !Ref 'ContainerCpu'
Memory: !Ref 'ContainerMemory'
Image: !Ref 'ImageUrl'
Environment:
- Name: SPRING_DATASOURCE_URL
Value: !Join
- ''
- - 'jdbc:postgresql://'
- Fn::ImportValue: !Join [':', [!Ref 'DatabaseStackName', 'EndpointAddress']]
- ':'
- Fn::ImportValue: !Join [':', [!Ref 'DatabaseStackName', 'EndpointPort']]
- '/'
- Fn::ImportValue: !Join [':', [!Ref 'DatabaseStackName', 'DBName']]
- Name: SPRING_DATASOURCE_USERNAME
Value: !Join
- ''
- - '{{resolve:secretsmanager:'
- Fn::ImportValue: !Join [':', [!Ref 'DatabaseStackName', 'Secret']]
- ':SecretString:username}}'
- Name: SPRING_DATASOURCE_PASSWORD
Value: !Join
- ''
- - '{{resolve:secretsmanager:'
- Fn::ImportValue: !Join [':', [!Ref 'DatabaseStackName', 'Secret']]
- ':SecretString:password}}'
PortMappings:
- ContainerPort: !Ref 'ContainerPort'
LogConfiguration:
LogDriver: 'awslogs'
Options:
awslogs-group: !Ref 'ServiceName'
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: !Ref 'ServiceName'
Service:
Type: AWS::ECS::Service
DependsOn: LoadBalancerRule
Properties:
ServiceName: !Ref 'ServiceName'
Cluster:
Fn::ImportValue:
!Join [':', [!Ref 'NetworkStackName', 'ClusterName']]
LaunchType: FARGATE
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 50
DesiredCount: !Ref 'DesiredCount'
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- Fn::ImportValue:
!Join [':', [!Ref 'NetworkStackName', 'ECSSecurityGroup']]
Subnets:
- Fn::ImportValue:
!Join [':', [!Ref 'NetworkStackName', 'PublicSubnetOne']]
- Fn::ImportValue:
!Join [':', [!Ref 'NetworkStackName', 'PublicSubnetTwo']]
TaskDefinition: !Ref 'TaskDefinition'
LoadBalancers:
- ContainerName: !Ref 'ServiceName'
ContainerPort: !Ref 'ContainerPort'
TargetGroupArn: !Ref 'TargetGroup'

View File

@@ -10,7 +10,7 @@ build_gradle_module() {
echo "+++"
cd $MODULE_PATH && {
chmod +x gradlew
./gradlew clean build --info --stacktrace
./gradlew clean build
if [ $? -ne 0 ]
then
echo ""
@@ -54,9 +54,9 @@ build_maven_module() {
}
}
chmod +x gradlew
build_maven_module "spring-boot/dependency-injection"
build_maven_module "spring-boot/spring-boot-openapi"
build_maven_module "spring-boot/data-migration/liquibase"
build_gradle_module "spring-boot/boundaries"
build_gradle_module "spring-boot/argumentresolver"
build_gradle_module "spring-data/spring-data-jdbc-converter"
@@ -85,6 +85,7 @@ build_gradle_module "spring-boot/static"
build_gradle_module "spring-boot/validation"
build_gradle_module "spring-boot/profiles"
build_gradle_module "spring-boot/password-encoding"
build_gradle_module "spring-boot/testcontainers"
build_gradle_module "spring-cloud/feign-with-spring-data-rest"
build_gradle_module "spring-cloud/sleuth-downstream-service"
build_gradle_module "spring-cloud/sleuth-upstream-service"

View File

@@ -0,0 +1,13 @@
package io.reflectoring.solid.isp;
class AdaptedBurgerOrder implements IAdapterOrderForBurger {
private final IOrder burgerOrder;
public AdaptedBurgerOrder(IOrder burgerOrder){
this.burgerOrder = burgerOrder;
}
@Override
public void orderBurger(int quantity) {
burgerOrder.orderBurger(quantity);
}
}

View File

@@ -0,0 +1,13 @@
package io.reflectoring.solid.isp;
class AdaptedFriesOrder implements IAdapterOrderForFries {
private final IOrder friesOrder;
public AdaptedFriesOrder(IOrder friesOrder){
this.friesOrder = friesOrder;
}
@Override
public void orderFries(int quantity) {
friesOrder.orderFries(quantity);
}
}

View File

@@ -0,0 +1,13 @@
package io.reflectoring.solid.isp;
class BurgerOrder implements IOrder {
@Override
public void orderBurger(int quantity) {
}
@Override
public void orderFries(int fries) {
throw new UnsupportedOperationException("No fries in Burger only order");
}
}

View File

@@ -0,0 +1,5 @@
package io.reflectoring.solid.isp;
interface IAdapterOrderForBurger {
void orderBurger(int quantity);
}

View File

@@ -0,0 +1,5 @@
package io.reflectoring.solid.isp;
interface IAdapterOrderForFries {
void orderFries(int quantity);
}

View File

@@ -0,0 +1,5 @@
package io.reflectoring.solid.isp;
interface IBurgerOrder{
void orderBurger(int quantity);
}

View File

@@ -0,0 +1,5 @@
package io.reflectoring.solid.isp;
interface IFriesOrder {
void orderFries(int quantity);
}

View File

@@ -0,0 +1,6 @@
package io.reflectoring.solid.isp;
interface IOrder {
void orderBurger(int quantity);
void orderFries(int fries);
}

View File

@@ -32,12 +32,7 @@ class GitRepositoryArgumentResolver implements HandlerMethodArgumentResolver {
.substring(0, requestPath.indexOf("/", 1))
.replaceAll("^/", "");
Optional<GitRepository> repository = gitRepositoryFinder.findBySlug(slug);
if (repository.isEmpty()) {
throw new NotFoundException();
}
return repository.get();
return gitRepositoryFinder.findBySlug(slug)
.orElseThrow(NotFoundException::new);
}
}

View File

@@ -0,0 +1,17 @@
package io.reflectoring.argumentresolver;
import lombok.Value;
@Value
class GitRepositoryIdWithValueOf {
private final long value;
/**
* If we have a static valueOf(String) method, we don't need a Converter!
*/
public static GitRepositoryIdWithValueOf valueOf(String value){
return new GitRepositoryIdWithValueOf(Long.parseLong(value));
}
}

View File

@@ -0,0 +1,27 @@
package io.reflectoring.argumentresolver;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(controllers = GitRepositoryIdWithValueOfTestController.class)
class GitRepositoryIdWithValueOfTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private GitRepositoryFinder gitRepositoryFinder;
@Test
void resolvesRepositoryId() throws Exception {
mockMvc.perform(get("/repositories/42"))
.andExpect(status().isOk());
}
}

View File

@@ -0,0 +1,18 @@
package io.reflectoring.argumentresolver;
import static org.assertj.core.api.Assertions.assertThat;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
class GitRepositoryIdWithValueOfTestController {
@GetMapping("/repositories/{repositoryId}")
String getRepository(@PathVariable("repositoryId") GitRepositoryIdWithValueOf gitRepositoryId) {
assertThat(gitRepositoryId).isNotNull();
return "test";
}
}

View File

@@ -7,6 +7,6 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@Configuration
@EnableScheduling
@ComponentScan
class BatchJobConfiguration {
class BillingBatchJobConfiguration {
}

View File

@@ -28,7 +28,6 @@ class InternalPackageTests {
List<String> internalPackages = internalPackages(BASE_PACKAGE);
for (String internalPackage : internalPackages) {
assertPackageExists(internalPackage);
assertPackageIsNotAccessedFromOutside(internalPackage);
}

View File

@@ -1,5 +1,5 @@
plugins {
id 'org.springframework.boot' version '2.1.3.RELEASE'
id 'org.springframework.boot' version '2.3.0.RELEASE'
id 'java'
}
@@ -7,7 +7,7 @@ apply plugin: 'io.spring.dependency-management'
group = 'io.reflectoring'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
sourceCompatibility = '13'
repositories {
mavenCentral()
@@ -23,6 +23,7 @@ dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.4.0')
testImplementation('org.springframework.boot:spring-boot-starter-test'){
exclude group: 'junit', module: 'junit'
exclude group: 'org.junit.vintage'
}
}

View File

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

0
spring-boot/configuration/gradlew vendored Normal file → Executable file
View File

View File

@@ -0,0 +1,14 @@
package io.reflectoring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,26 @@
package io.reflectoring.validation;
import io.reflectoring.validation.thirdparty.ThirdPartyComponentProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
@Configuration
@EnableConfigurationProperties(AppProperties.class)
class AppConfiguration {
@Bean
public static ReportEmailAddressValidator configurationPropertiesValidator() {
return new ReportEmailAddressValidator();
}
@Bean
@Validated
@ConfigurationProperties(prefix = "app.third-party.properties")
public ThirdPartyComponentProperties thirdPartyComponentProperties() {
return new ThirdPartyComponentProperties();
}
}

View File

@@ -0,0 +1,53 @@
package io.reflectoring.validation;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
@Validated
@ConfigurationProperties(prefix = "app.properties")
class AppProperties implements Validator {
@NotBlank
private String name;
@Valid
private ReportProperties report;
private static final String APP_BASE_NAME = "Application";
public boolean supports(Class clazz) {
return AppProperties.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
AppProperties appProperties = (AppProperties) target;
if (!appProperties.getName().endsWith(APP_BASE_NAME)) {
errors.rejectValue("name", "field.name.malformed",
new Object[]{APP_BASE_NAME},
"The application name must contain [" + APP_BASE_NAME + "] base name");
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ReportProperties getReport() {
return report;
}
public void setReport(ReportProperties report) {
this.report = report;
}
}

View File

@@ -0,0 +1,27 @@
package io.reflectoring.validation;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
class ReportEmailAddressValidator implements Validator {
private static final String EMAIL_DOMAIN = "@analysisapp.com";
public boolean supports(Class clazz) {
return ReportProperties.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "emailAddress", "field.required");
ReportProperties reportProperties = (ReportProperties) target;
if (!reportProperties.getEmailAddress().endsWith(EMAIL_DOMAIN)) {
errors.rejectValue("emailAddress", "field.domain.required",
new Object[]{EMAIL_DOMAIN},
"The email address must contain [" + EMAIL_DOMAIN + "] domain");
}
}
}

View File

@@ -0,0 +1,51 @@
package io.reflectoring.validation;
import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
class ReportProperties {
private Boolean sendEmails = Boolean.FALSE;
private ReportType type = ReportType.HTML;
@Min(value = 7)
@Max(value = 30)
private Integer intervalInDays;
@Email
private String emailAddress;
public Boolean getSendEmails() {
return sendEmails;
}
public void setSendEmails(Boolean sendEmails) {
this.sendEmails = sendEmails;
}
public ReportType getType() {
return type;
}
public void setType(ReportType type) {
this.type = type;
}
public Integer getIntervalInDays() {
return intervalInDays;
}
public void setIntervalInDays(Integer intervalInDays) {
this.intervalInDays = intervalInDays;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
}

View File

@@ -0,0 +1,5 @@
package io.reflectoring.validation;
enum ReportType {
HTML, PLAIN_TEXT
}

View File

@@ -0,0 +1,21 @@
package io.reflectoring.validation.thirdparty;
import javax.validation.constraints.NotBlank;
/**
* We assume that this bean comes from another jar file
*/
public class ThirdPartyComponentProperties {
@NotBlank
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,7 @@
app.properties.name=Analysis Application
app.properties.report.send-emails=true
app.properties.report.type=PLAIN_TEXT
app.properties.report.interval-in-days=14
app.properties.report.email-address=manager@analysisapp.com
# third-party component properties
app.third-party.properties.name=Third Party!

View File

@@ -1,5 +1,4 @@
myapp.mail.enabled=true
myapp
myapp.mail.pauseBetweenMails=5s
myapp.mail.maxAttachmentSize=1MB
myapp.mail.smtpServers[0]=server1

View File

@@ -1,37 +1,36 @@
package io.reflectoring.configuration.mail;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.unit.DataSize;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.util.unit.DataSize;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(properties = {
"myapp.mail.enabled=asd",
"myapp.mail.defaultSubject=hello",
"myapp.mail.pauseBetweenMails=5s",
"myapp.mail.maxAttachmentSize=1MB",
"myapp.mail.smtpServers[0]=server1",
"myapp.mail.smtpServers[1]=server2",
"myapp.mail.maxAttachmentWeight=5kg"
"myapp.mail.enabled=asd",
"myapp.mail.defaultSubject=hello",
"myapp.mail.pauseBetweenMails=5s",
"myapp.mail.maxAttachmentSize=1MB",
"myapp.mail.smtpServers[0]=server1",
"myapp.mail.smtpServers[1]=server2",
"myapp.mail.maxAttachmentWeight=5kg"
})
class MailModuleTestWithAllProperties {
@Autowired(required = false)
private MailModuleProperties mailModuleProperties;
@Autowired(required = false)
private MailModuleProperties mailModuleProperties;
@Test
void propertiesAreLoaded() {
assertThat(mailModuleProperties).isNotNull();
assertThat(mailModuleProperties.getDefaultSubject()).isEqualTo("hello");
assertThat(mailModuleProperties.getEnabled()).isTrue();
assertThat(mailModuleProperties.getPauseBetweenMails()).isEqualByComparingTo(Duration.ofSeconds(5));
assertThat(mailModuleProperties.getMaxAttachmentSize()).isEqualByComparingTo(DataSize.ofMegabytes(1));
assertThat(mailModuleProperties.getSmtpServers()).hasSize(2);
assertThat(mailModuleProperties.getMaxAttachmentWeight().getGrams()).isEqualTo(5000L);
}
@Test
void propertiesAreLoaded() {
assertThat(mailModuleProperties).isNotNull();
assertThat(mailModuleProperties.getDefaultSubject()).isEqualTo("hello");
assertThat(mailModuleProperties.getEnabled()).isTrue();
assertThat(mailModuleProperties.getPauseBetweenMails()).isEqualByComparingTo(Duration.ofSeconds(5));
assertThat(mailModuleProperties.getMaxAttachmentSize()).isEqualByComparingTo(DataSize.ofMegabytes(1));
assertThat(mailModuleProperties.getSmtpServers()).hasSize(2);
assertThat(mailModuleProperties.getMaxAttachmentWeight().getGrams()).isEqualTo(5000L);
}
}

View File

@@ -0,0 +1,127 @@
package io.reflectoring.validation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindException;
import org.springframework.boot.context.properties.bind.validation.BindValidationException;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.StandardEnvironment;
import java.util.Properties;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* We create Spring Application dynamically to catch and test application context startup exceptions
*/
class PropertiesInvalidInputTest {
SpringApplication application;
Properties properties;
@BeforeEach
void setup() {
// create Spring Application dynamically
application = new SpringApplication(ValidationApplication.class);
// setting test properties for our Spring Application
properties = new Properties();
ConfigurableEnvironment environment = new StandardEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.addFirst(new PropertiesPropertySource("application-test", properties));
application.setEnvironment(environment);
}
@Test
void whenGivenNameEmpty_thenNotBlankValidationFails() {
properties.put("app.properties.name", "");
assertThatThrownBy(application::run)
.isInstanceOf(ConfigurationPropertiesBindException.class)
.hasRootCauseInstanceOf(BindValidationException.class)
.hasStackTraceContaining("Field error in object 'app.properties' on field 'name'")
.hasStackTraceContaining("[must not be blank]");
}
@Test
void whenGivenNameDoesNotContainBaseName_thenCustomAppPropertiesValidatorFails() {
properties.put("app.properties.name", "My App");
assertThatThrownBy(application::run)
.isInstanceOf(ConfigurationPropertiesBindException.class)
.hasRootCauseInstanceOf(BindValidationException.class)
.hasStackTraceContaining("Field error in object 'app.properties' on field 'name'")
.hasStackTraceContaining("[The application name must contain [Application] base name]");
}
@Test
void whenGivenReportIntervalInDaysMoreThan30_thenMaxValidationFails() {
properties.put("app.properties.report.interval-in-days", "31");
assertThatThrownBy(application::run)
.isInstanceOf(ConfigurationPropertiesBindException.class)
.hasRootCauseInstanceOf(BindValidationException.class)
.hasStackTraceContaining("rejected value [31]");
}
@Test
void whenGivenReportIntervalInDaysLessThan7_thenMinValidationFails() {
properties.put("app.properties.report.interval-in-days", "6");
assertThatThrownBy(application::run)
.isInstanceOf(ConfigurationPropertiesBindException.class)
.hasRootCauseInstanceOf(BindValidationException.class)
.hasStackTraceContaining("rejected value [6]");
}
@Test
void whenGivenReportEmailAddressIsNotWellFormed_thenEmailValidationFails() {
properties.put("app.properties.report.email-address", "manager.analysisapp.com");
assertThatThrownBy(application::run)
.isInstanceOf(ConfigurationPropertiesBindException.class)
.hasRootCauseInstanceOf(BindValidationException.class)
.hasStackTraceContaining("rejected value [manager.analysisapp.com]");
}
@Test
void whenGivenReportEmailAddressDoesNotContainAnalysisappDomain_thenCustomEmailValidatorFails() {
properties.put("app.properties.report.email-address", "manager@notanalysisapp.com");
assertThatThrownBy(application::run)
.isInstanceOf(ConfigurationPropertiesBindException.class)
.hasRootCauseInstanceOf(BindValidationException.class)
.hasStackTraceContaining("Field error in object 'app.properties.report' on field 'emailAddress'")
.hasStackTraceContaining("[The email address must contain [@analysisapp.com] domain]");
}
@Test
void whenGivenThirdPartyComponentNameIsEmpty_thenNotBlankValidationFails() {
properties.put("app.third-party.properties.name", "");
assertThatThrownBy(application::run)
.isInstanceOf(ConfigurationPropertiesBindException.class)
.hasRootCauseInstanceOf(BindValidationException.class)
.hasStackTraceContaining("Field error in object 'app.third-party.properties' on field 'name'")
.hasStackTraceContaining("[must not be blank]");
}
}

View File

@@ -0,0 +1,43 @@
package io.reflectoring.validation;
import io.reflectoring.validation.thirdparty.ThirdPartyComponentProperties;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(properties = {
"app.properties.name=My Test Application",
"app.properties.report.send-emails=true",
"app.properties.report.type=PLAIN_TEXT",
"app.properties.report.interval-in-days=14",
"app.properties.report.email-address=manager@analysisapp.com",
"app.third-party.properties.name=Third Party!"
}, classes = {AppConfiguration.class})
class PropertiesValidInputTest {
@Autowired
AppProperties appProperties;
@Autowired
ThirdPartyComponentProperties thirdPartyComponentProperties;
@Test
void appPropertiesAreLoaded() {
assertThat(appProperties).isNotNull();
assertThat(appProperties.getName()).isEqualTo("My Test Application");
assertThat(appProperties.getReport()).isNotNull();
assertThat(appProperties.getReport().getSendEmails()).isTrue();
assertThat(appProperties.getReport().getType()).isEqualTo(ReportType.PLAIN_TEXT);
assertThat(appProperties.getReport().getIntervalInDays()).isEqualTo(14);
assertThat(appProperties.getReport().getEmailAddress()).isEqualTo("manager@analysisapp.com");
}
@Test
void thirdPartyComponentPropertiesAreLoaded() {
assertThat(thirdPartyComponentProperties).isNotNull();
assertThat(thirdPartyComponentProperties.getName()).isEqualTo("Third Party!");
}
}

View File

@@ -0,0 +1,15 @@
package io.reflectoring.validation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
@SpringBootApplication
@PropertySource("classpath:application-validation.properties")
public class ValidationApplication {
public static void main(String[] args) {
SpringApplication.run(ValidationApplication.class, args);
}
}

View File

@@ -0,0 +1,31 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**
### 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/
### VS Code ###
.vscode/

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

Binary file not shown.

View File

@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

View File

@@ -0,0 +1,122 @@
# liquibase-demo
This project demonstrates on how to set-up liquibase in a Spring Boot application.
When this project is run, it executes the changelogs and creates a `user_details` table and populates it with some test data.
It also creates an index and couple of constraints on `user_details` table.
The project also demonstrates the following.
* Organising liquibase change logs [using master changelog](src/main/resources/db/changelog/db.changelog-master.yaml)
* It demonstrates how to write a [change log in YAML format](src/main/resources/db/changelog/db.changelog-yaml-example.yaml)
* It demonstrates how to write a [change log in JSON format](src/main/resources/db/changelog/db.changelog-json-example.json)
* It demonstrates how to write a [change log in XML format](src/main/resources/db/changelog/db.changelog-xml-example.xml)
* It demonstrates how to write a [change log in SQL format](src/main/resources/db/changelog/db.changelog-sql-example.sql)
* Enable `INFO` [logs for liquibase](src/main/resources/application.yaml#L10-L12).
* It demonstrates testing of changes done by liquibase to the [schema using Junit test](src/test/java/io/reflectoring/liquibase/adapter/datastore/UserRepositoryDockerProfileTest.java)
* It demonstrates use of liquibase [change log parameters](src/main/resources/db/changelog/db.changelog-yaml-example.yaml#L21-L33)
* IT demonstrates use of liquibase [context](src/main/resources/db/changelog/db.changelog-xml-example.xml#L5)
## How to Run the project and tests
### Have Docker and Docker Compose in your machine?
#### Run application
- Clone this repository
```
git clone https://github.com/thombergs/code-examples/spring-boot/data-migration/liquibase
```
- Move to the directory `code-examples/spring-boot/data-migration/liquibase`
- Run the docker compose file `infra-local.yaml` in `src/docker`. This starts the postgres database needed to run the application.
```
docker-compose -f ./src/main/docker/infra-local.yaml up -d
```
- Now run the spring boot application as follows.
```
mvnw spring-boot:run -Dspring-boot.run.profiles=docker
```
The application should start and listening on port `8080`.
Open the url (http://localhost:8080/liquibase/users/100000000) in the browser. You should see a response as below
```json
{
"id": 100000000,
"userName": "testUser",
"firstName": "testFirstName",
"lastName": "testLastName"
}
```
You can login to the database and check the creation of `user_details` table along with some test data in it.
```yaml
#Use below credentials to login to the database
databaseHost: localhost
jdbcUrl: jdbc:postgresql://localhost:5432/liquibasedemo?current_schema=public
port: 5432
username: demouser
password: demopassword
```
Also, have a look at databasechangelog and databasechangeloglock tables.
#### Run Test
To run integration test, you don't need to run the docker compose file. Just run the test as follows:
```yaml
mvn test -Dspring.profiles.active=docker
```
The test uses [TestContainers](https://www.testcontainers.org/) to spin a postgres database, which is used during the integration test.
### Don't have docker?
You can use h2 profile to run the app and use h2 database.
- Clone this repository
```
git clone https://github.com/thombergs/code-examples/spring-boot/data-migration/liquibase
```
- Move to the directory `code-examples/spring-boot/data-migration/liquibase`
- Now run the spring boot application as follows.
```
mvnw spring-boot:run -Dspring-boot.run.profiles=h2
```
The application should start and listening on port `8080`.
Open the url (http://localhost:8080/liquibase/users/100000000) in the browser. You should see a response as below
```json
{
"id": 100000000,
"userName": "testUser",
"firstName": "testFirstName",
"lastName": "testLastName"
}
```
Login to h2 database console using [url](http://localhost:8080/liquibase/h2-console)
```yaml
JDBC URL: jdbc:h2:mem:liquibasedemo
username: demouser
password: demopassword
```
Check the creation of `user_details` table along with some test data in it.
Also, have a look at databasechangelog and databasechangeloglock tables.
#### Run Test
To run integration test
```yaml
mvn test -Dspring.profiles.active=h2
```

View File

@@ -0,0 +1,310 @@
#!/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.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# 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 /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
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -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 "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; 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="`which 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
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# 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/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
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 "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -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
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

View File

@@ -0,0 +1,182 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM 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 Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a 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 "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.reflectoring</groupId>
<artifactId>liquibase</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>liquibase</name>
<description>Code example for Spring Boot liquibase</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.15.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.13.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.13.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.13.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,12 @@
version: "3.0"
services:
database:
image: library/postgres:latest
ports:
- 5432:5432
expose:
- 5432
environment:
- POSTGRES_PASSWORD=demopassword
- POSTGRES_USER=demouser
- POSTGRES_DB=liquibasedemo

View File

@@ -0,0 +1,13 @@
package io.reflectoring.liquibase;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LiquibaseApplication {
public static void main(String[] args) {
SpringApplication.run(LiquibaseApplication.class, args);
}
}

View File

@@ -0,0 +1,33 @@
package io.reflectoring.liquibase.adapter.api;
import io.reflectoring.liquibase.adapter.datastore.UserRepository;
import io.reflectoring.liquibase.domain.User;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
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;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@PostMapping
public User addUser(@RequestBody User user) {
return userRepository.save(user);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable("id") Long userId) {
return ResponseEntity.of(userRepository.findById(userId));
}
}

View File

@@ -0,0 +1,7 @@
package io.reflectoring.liquibase.adapter.datastore;
import io.reflectoring.liquibase.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}

View File

@@ -0,0 +1,59 @@
package io.reflectoring.liquibase.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "user_details")
public class User {
@Id
@Column(name ="id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username")
private String userName;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}

View File

@@ -0,0 +1,43 @@
spring:
profiles:
active: h2
application:
name: liquibase
server:
servlet:
context-path: /liquibase
logging:
level:
"liquibase" : info
---
spring:
profiles: docker
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
datasource:
url: jdbc:postgresql://localhost:5432/liquibasedemo?current_schema=public
driverClassName: org.postgresql.Driver
username: demouser
password: demopassword
liquibase:
parameters:
textColumnType: TEXT
contexts: nonprod
---
spring:
profiles: h2
jpa:
database-platform: org.hibernate.dialect.H2Dialect
datasource:
url: jdbc:h2:mem:liquibasedemo
driverClassName: org.h2.Driver
username: demouser
password: demopassword
liquibase:
parameters:
textColumnType: VARCHAR(250)
contexts: nonprod
h2:
console:
enabled: true

View File

@@ -0,0 +1,16 @@
{ "databaseChangeLog": [
{
"changeSet": {
"id": "add-last-name-constraint",
"author": "liquibase-demo-service",
"changes": [
{
"addNotNullConstraint": {
"columnName": "last_name",
"constraintName": "user_last_name_key",
"tableName": "user_details"
}
}]
}
}
]}

View File

@@ -0,0 +1,9 @@
databaseChangeLog:
- include:
file: db/changelog/db.changelog-yaml-example.yaml
- include:
file: db/changelog/db.changelog-sql-example.sql
- include:
file: db/changelog/db.changelog-json-example.json
- include:
file: db/changelog/db.changelog-xml-example.xml

View File

@@ -0,0 +1,4 @@
--liquibase formatted sql
--changeset liquibase-demo-service:add-user-name-constraint
ALTER TABLE user_details ADD CONSTRAINT user_details_username_key UNIQUE (username);

View File

@@ -0,0 +1,16 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd">
<changeSet author="liquibase-docs" id="loadUpdateData-example" context="!prod">
<loadUpdateData
encoding="UTF-8"
file="db/data/users.csv"
onlyUpdate="false"
primaryKey="id"
quotchar="'"
separator=","
tableName="user_details">
</loadUpdateData>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,34 @@
databaseChangeLog:
- changeSet:
id: create-table-user
author: liquibase-demo-service
preConditions:
- onFail: MARK_RAN
not:
tableExists:
tableName: user_details
changes:
- createTable:
columns:
- column:
autoIncrement: true
constraints:
nullable: false
primaryKey: true
primaryKeyName: user_pkey
name: id
type: BIGINT
- column:
constraints:
nullable: false
name: username
type: ${textColumnType}
- column:
constraints:
nullable: false
name: first_name
type: ${textColumnType}
- column:
name: last_name
type: ${textColumnType}
tableName: user_details

View File

@@ -0,0 +1,3 @@
id,username,first_name,last_name
100000000,testUser,testFirstName,testLastName
100000001,testUser1,testFirstName1,testLastName1
1 id username first_name last_name
2 100000000 testUser testFirstName testLastName
3 100000001 testUser1 testFirstName1 testLastName1

View File

@@ -0,0 +1,19 @@
Configuration:
status: error
appenders:
Console:
name: Console
PatternLayout:
Pattern: "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"
Loggers:
logger:
- name: io.reflectoring
level: debug
additivity: false
AppenderRef:
- ref: Console
Root:
level: error
AppenderRef:
ref: Console

View File

@@ -0,0 +1,67 @@
package io.reflectoring.liquibase.adapter.datastore;
import static org.assertj.core.api.Assertions.assertThat;
import io.reflectoring.liquibase.domain.User;
import java.util.Optional;
import javax.validation.constraints.NotNull;
import org.junit.ClassRule;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Profiles;
import org.springframework.test.context.ContextConfiguration;
import org.testcontainers.containers.PostgreSQLContainer;
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ContextConfiguration(initializers = UserRepositoryIntegrationTest.Initializer.class)
class UserRepositoryIntegrationTest {
@ClassRule
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer()
.withDatabaseName("liquibasedemo").withPassword("demopassword").withUsername("demouser");
@Autowired
private UserRepository userRepository;
@Test
public void findById_userId100000000_returnsUserDetails() {
Optional<User> user = userRepository.findById(100000000L);
assertThat(user.isPresent()).isTrue();
assertThat(user.get()).hasFieldOrPropertyWithValue("id", 100000000L)
.hasFieldOrPropertyWithValue("userName", "testUser");
}
@Test
public void findById_userId100000001_returnsUserDetails() {
Optional<User> user = userRepository.findById(100000001L);
assertThat(user.isPresent()).isTrue();
assertThat(user.get()).hasFieldOrPropertyWithValue("id", 100000001L)
.hasFieldOrPropertyWithValue("userName", "testUser1");
}
/**
* This used to override the database properties in spring environment
*/
public static class Initializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(@NotNull ConfigurableApplicationContext configurableApplicationContext) {
if (configurableApplicationContext.getEnvironment().acceptsProfiles(Profiles.of("docker"))) {
postgreSQLContainer.start();
configurableApplicationContext.getEnvironment().getSystemProperties()
.put("spring.datasource.url", postgreSQLContainer.getJdbcUrl());
configurableApplicationContext.getEnvironment().getSystemProperties()
.put("spring.datasource.username", postgreSQLContainer.getUsername());
configurableApplicationContext.getEnvironment().getSystemProperties()
.put("spring.datasource.password", postgreSQLContainer.getPassword());
}
}
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

Binary file not shown.

View File

@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

310
spring-boot/dependency-injection/mvnw vendored Normal file
View File

@@ -0,0 +1,310 @@
#!/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.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# 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 /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
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -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 "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; 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="`which 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
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# 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/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
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 "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -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
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

View File

@@ -0,0 +1,182 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM 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 Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a 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 "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>Example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Example</name>
<description>Demo project for dependency injection</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,15 @@
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication(scanBasePackages = { "com.example.dependency" })
public class ExampleApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(ExampleApplication.class, args);
}
}

View File

@@ -0,0 +1,29 @@
package com.example.constructorinjection;
import java.util.Objects;
import org.springframework.stereotype.Component;
import com.example.dependency.Flavor;
@Component
public class Cake {
private Flavor flavor;
Cake(Flavor flavor) {
Objects.requireNonNull(flavor);
this.flavor = flavor;
}
public Flavor getFlavor() {
return flavor;
}
@Override
public String toString() {
return "Cake [flavor=" + flavor + "]";
}
}

View File

@@ -0,0 +1,32 @@
package com.example.constructorinjection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.example.dependency.Bread;
import com.example.dependency.Topping;
@Component
public class Sandwich {
private Topping toppings;
private Bread breadType;
Sandwich(Topping toppings) {
this.toppings = toppings;
}
@Autowired
Sandwich(Topping toppings, Bread breadType) {
this.toppings = toppings;
this.breadType = breadType;
}
public Topping getToppings() {
return toppings;
}
public Bread getBreadType() {
return breadType;
}
}

View File

@@ -0,0 +1,17 @@
package com.example.dependency;
import org.springframework.stereotype.Component;
@Component
public class Bread {
String breadType;
public String getBreadType() {
return breadType;
}
void setBreadType(String breadType) {
this.breadType = breadType;
}
}

View File

@@ -0,0 +1,24 @@
package com.example.dependency;
import org.springframework.stereotype.Component;
@Component
public class Flavor {
String flavorType;
String color;
public String getFlavorType() {
return flavorType;
}
public String getColor() {
return color;
}
@Override
public String toString() {
return "Flavor [flavorType=" + flavorType + ", color=" + color + "]";
}
}

View File

@@ -0,0 +1,24 @@
package com.example.dependency;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Pizza {
@Autowired
private Topping toppings;
Pizza(Topping toppings) {
this.toppings = toppings;
}
public Topping getToppings() {
return toppings;
}
public void setToppings(Topping toppings) {
this.toppings = toppings;
}
}

View File

@@ -0,0 +1,23 @@
package com.example.dependency;
import org.springframework.stereotype.Component;
@Component
public class Topping {
String toppingName;
public String getToppingName() {
return toppingName;
}
void setToppingName(String toppingName) {
this.toppingName = toppingName;
}
@Override
public String toString() {
return "Topping [toppingName=" + toppingName + "]";
}
}

View File

@@ -0,0 +1,28 @@
package com.example.fieldinjection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.example.dependency.Topping;
@Component
public class IceCream {
@Autowired
private Topping toppings;
public Topping getToppings() {
return toppings;
}
void setToppings(Topping toppings) {
System.out.println("Using setter injection - " + this.toppings);
this.toppings = toppings;
}
@Override
public String toString() {
return "IceCream [toppings=" + toppings + "]";
}
}

View File

@@ -0,0 +1,50 @@
package com.example.setterinjection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.example.dependency.Flavor;
import com.example.dependency.Topping;
@Component
public class Cake {
private Logger LOGGER = LoggerFactory.getLogger(Cake.class);
private Flavor flavor;
@Autowired
private Topping toppings;
public Cake() {
LOGGER.info("Flavor from setter Injection : " + this.flavor);
}
@Autowired
public void setFlavor(Flavor flavor) {
LOGGER.info("Initialising flavor object using setter injection");
this.flavor = flavor;
}
public Flavor getFlavor() {
return flavor;
}
public Topping getToppings() {
return toppings;
}
public void setToppings(Topping toppings) {
this.toppings = toppings;
}
@Override
public String toString() {
return "Cake [LOGGER=" + LOGGER + ", flavor=" + flavor + ", toppings=" + toppings + "]";
}
}

View File

@@ -0,0 +1,27 @@
package com.example.setterinjection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.example.dependency.Topping;
@Component
public class Cookie {
private Topping toppings;
@Autowired
void setTopping(Topping toppings) {
this.toppings = toppings;
}
public Topping getTopping() {
return toppings;
}
@Override
public String toString() {
return "Cookie [toppings=" + toppings + "]";
}
}

View File

@@ -0,0 +1,19 @@
package com.example;
import com.example.constructorinjection.Cake;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication(scanBasePackages = { "com.example.constructorinjection", "com.example.dependency" })
public class ExampleApplicationCI {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(ExampleApplicationCI.class, args);
Cake obj = context.getBean(Cake.class);
System.out.println("Cake : " + obj.toString());
}
}

View File

@@ -0,0 +1,19 @@
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import com.example.fieldinjection.IceCream;
@SpringBootApplication(scanBasePackages = { "com.example.dependency", "com.example.fieldinjection" })
public class ExampleApplicationFI {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(ExampleApplicationFI.class, args);
IceCream obj = context.getBean(IceCream.class);
System.out.println("IceCream : " + obj.toString());
}
}

View File

@@ -0,0 +1,19 @@
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import com.example.setterinjection.Cookie;
@SpringBootApplication(scanBasePackages = { "com.example.setterinjection", "com.example.dependency", })
public class ExampleApplicationSI {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(ExampleApplicationSI.class, args);
Cookie obj = context.getBean(Cookie.class);
System.out.println("Cookie : " + obj.toString());
}
}

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