diff --git a/aws-app-sync/README.md b/aws-app-sync/README.md
new file mode 100644
index 0000000000..976a999f40
--- /dev/null
+++ b/aws-app-sync/README.md
@@ -0,0 +1,3 @@
+### Relevant Articles:
+
+- [AWS AppSync With Spring Boot](https://www.baeldung.com/aws-appsync-spring)
diff --git a/core-java-modules/core-java-8-2/pom.xml b/core-java-modules/core-java-8-2/pom.xml
index 00579c49b2..48474a5eef 100644
--- a/core-java-modules/core-java-8-2/pom.xml
+++ b/core-java-modules/core-java-8-2/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-8-2
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-8-datetime-2/pom.xml b/core-java-modules/core-java-8-datetime-2/pom.xml
index ce98b72781..629ce5234d 100644
--- a/core-java-modules/core-java-8-datetime-2/pom.xml
+++ b/core-java-modules/core-java-8-datetime-2/pom.xml
@@ -8,12 +8,11 @@
${project.parent.version}
core-java-8-datetime
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-8-datetime/pom.xml b/core-java-modules/core-java-8-datetime/pom.xml
index ce98b72781..629ce5234d 100644
--- a/core-java-modules/core-java-8-datetime/pom.xml
+++ b/core-java-modules/core-java-8-datetime/pom.xml
@@ -8,12 +8,11 @@
${project.parent.version}
core-java-8-datetime
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-8/pom.xml b/core-java-modules/core-java-8/pom.xml
index a434be028d..557f9e0dce 100644
--- a/core-java-modules/core-java-8/pom.xml
+++ b/core-java-modules/core-java-8/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-8
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-9-streams/pom.xml b/core-java-modules/core-java-9-streams/pom.xml
index 7865b336a7..8c1af89b24 100644
--- a/core-java-modules/core-java-9-streams/pom.xml
+++ b/core-java-modules/core-java-9-streams/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-9-streams
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-annotations/pom.xml b/core-java-modules/core-java-annotations/pom.xml
index 8fc4c15cde..92ba4991bb 100644
--- a/core-java-modules/core-java-annotations/pom.xml
+++ b/core-java-modules/core-java-annotations/pom.xml
@@ -10,10 +10,10 @@
jar
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-arrays-convert/pom.xml b/core-java-modules/core-java-arrays-convert/pom.xml
index bd50289f47..67dc645936 100644
--- a/core-java-modules/core-java-arrays-convert/pom.xml
+++ b/core-java-modules/core-java-arrays-convert/pom.xml
@@ -5,7 +5,7 @@
core-java-modules
com.baeldung.core-java-modules
- 1.0.0-SNAPSHOT
+ 0.0.1-SNAPSHOT
4.0.0
diff --git a/core-java-modules/core-java-arrays-guides/pom.xml b/core-java-modules/core-java-arrays-guides/pom.xml
index ef718d5117..df8639820d 100644
--- a/core-java-modules/core-java-arrays-guides/pom.xml
+++ b/core-java-modules/core-java-arrays-guides/pom.xml
@@ -5,7 +5,7 @@
core-java-modules
com.baeldung.core-java-modules
- 1.0.0-SNAPSHOT
+ 0.0.1-SNAPSHOT
4.0.0
diff --git a/core-java-modules/core-java-arrays-multidimensional/pom.xml b/core-java-modules/core-java-arrays-multidimensional/pom.xml
index 6e49a20521..d90853678c 100644
--- a/core-java-modules/core-java-arrays-multidimensional/pom.xml
+++ b/core-java-modules/core-java-arrays-multidimensional/pom.xml
@@ -5,7 +5,7 @@
core-java-modules
com.baeldung.core-java-modules
- 1.0.0-SNAPSHOT
+ 0.0.1-SNAPSHOT
4.0.0
diff --git a/core-java-modules/core-java-arrays-operations-advanced/pom.xml b/core-java-modules/core-java-arrays-operations-advanced/pom.xml
index 8989e91189..d73fdcee28 100644
--- a/core-java-modules/core-java-arrays-operations-advanced/pom.xml
+++ b/core-java-modules/core-java-arrays-operations-advanced/pom.xml
@@ -5,7 +5,7 @@
core-java-modules
com.baeldung.core-java-modules
- 1.0.0-SNAPSHOT
+ 0.0.1-SNAPSHOT
4.0.0
diff --git a/core-java-modules/core-java-arrays-operations-basic/pom.xml b/core-java-modules/core-java-arrays-operations-basic/pom.xml
index 4480c14bb2..73588d662a 100644
--- a/core-java-modules/core-java-arrays-operations-basic/pom.xml
+++ b/core-java-modules/core-java-arrays-operations-basic/pom.xml
@@ -5,7 +5,7 @@
core-java-modules
com.baeldung.core-java-modules
- 1.0.0-SNAPSHOT
+ 0.0.1-SNAPSHOT
4.0.0
diff --git a/core-java-modules/core-java-arrays-sorting/pom.xml b/core-java-modules/core-java-arrays-sorting/pom.xml
index 127d921b2a..d5e2beaac4 100644
--- a/core-java-modules/core-java-arrays-sorting/pom.xml
+++ b/core-java-modules/core-java-arrays-sorting/pom.xml
@@ -5,8 +5,8 @@
core-java-modules
com.baeldung.core-java-modules
- 1.0.0-SNAPSHOT
- ../pom.xml
+ 0.0.1-SNAPSHOT
+ ../
4.0.0
diff --git a/core-java-modules/core-java-collections-2/pom.xml b/core-java-modules/core-java-collections-2/pom.xml
index 3a7c70b1a2..d163aabdbc 100644
--- a/core-java-modules/core-java-collections-2/pom.xml
+++ b/core-java-modules/core-java-collections-2/pom.xml
@@ -7,12 +7,11 @@
core-java-collections-2
core-java-collections-2
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-collections-3/pom.xml b/core-java-modules/core-java-collections-3/pom.xml
index 1e1695c8bc..bd991bfefa 100644
--- a/core-java-modules/core-java-collections-3/pom.xml
+++ b/core-java-modules/core-java-collections-3/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-collections-3
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-collections-array-list/pom.xml b/core-java-modules/core-java-collections-array-list/pom.xml
index 74a6513cac..81ee4eff55 100644
--- a/core-java-modules/core-java-collections-array-list/pom.xml
+++ b/core-java-modules/core-java-collections-array-list/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-collections-array-list
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-collections-list-2/pom.xml b/core-java-modules/core-java-collections-list-2/pom.xml
index 3184da1294..230787d14d 100644
--- a/core-java-modules/core-java-collections-list-2/pom.xml
+++ b/core-java-modules/core-java-collections-list-2/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-collections-list-2
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-collections-list-3/pom.xml b/core-java-modules/core-java-collections-list-3/pom.xml
index 090e756ac6..373190a130 100644
--- a/core-java-modules/core-java-collections-list-3/pom.xml
+++ b/core-java-modules/core-java-collections-list-3/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-collections-list-3
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-collections-list/pom.xml b/core-java-modules/core-java-collections-list/pom.xml
index e6dce5a0db..509f58ea61 100644
--- a/core-java-modules/core-java-collections-list/pom.xml
+++ b/core-java-modules/core-java-collections-list/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-collections-list
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-collections-maps-2/pom.xml b/core-java-modules/core-java-collections-maps-2/pom.xml
index a08a4ac072..a64a11c6ea 100644
--- a/core-java-modules/core-java-collections-maps-2/pom.xml
+++ b/core-java-modules/core-java-collections-maps-2/pom.xml
@@ -7,12 +7,11 @@
0.1.0-SNAPSHOT
core-java-collections-maps-2
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-collections-maps-3/pom.xml b/core-java-modules/core-java-collections-maps-3/pom.xml
index 95414c12c2..f547968b22 100644
--- a/core-java-modules/core-java-collections-maps-3/pom.xml
+++ b/core-java-modules/core-java-collections-maps-3/pom.xml
@@ -7,12 +7,11 @@
0.1.0-SNAPSHOT
core-java-collections-maps-3
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-collections-maps/pom.xml b/core-java-modules/core-java-collections-maps/pom.xml
index c0dd705c1c..742e264504 100644
--- a/core-java-modules/core-java-collections-maps/pom.xml
+++ b/core-java-modules/core-java-collections-maps/pom.xml
@@ -6,12 +6,11 @@
0.1.0-SNAPSHOT
core-java-collections-maps
jar
-
-
- com.baeldung
- parent-java
+
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-collections-set/pom.xml b/core-java-modules/core-java-collections-set/pom.xml
index c89ba0c091..7c55f2ff2a 100644
--- a/core-java-modules/core-java-collections-set/pom.xml
+++ b/core-java-modules/core-java-collections-set/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-collections-set
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-collections/pom.xml b/core-java-modules/core-java-collections/pom.xml
index 515d19d7fb..e1219c4713 100644
--- a/core-java-modules/core-java-collections/pom.xml
+++ b/core-java-modules/core-java-collections/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-collections
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-concurrency-2/pom.xml b/core-java-modules/core-java-concurrency-2/pom.xml
index dfb5674c8e..75fd3890b3 100644
--- a/core-java-modules/core-java-concurrency-2/pom.xml
+++ b/core-java-modules/core-java-concurrency-2/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-concurrency-2
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-concurrency-advanced-2/pom.xml b/core-java-modules/core-java-concurrency-advanced-2/pom.xml
index 8752e7b7db..2f374bffac 100644
--- a/core-java-modules/core-java-concurrency-advanced-2/pom.xml
+++ b/core-java-modules/core-java-concurrency-advanced-2/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-concurrency-advanced-2
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-concurrency-advanced-3/README.md b/core-java-modules/core-java-concurrency-advanced-3/README.md
index dfd264116c..e33b6ab692 100644
--- a/core-java-modules/core-java-concurrency-advanced-3/README.md
+++ b/core-java-modules/core-java-concurrency-advanced-3/README.md
@@ -12,4 +12,5 @@ This module contains articles about advanced topics about multithreading with co
- [Asynchronous Programming in Java](https://www.baeldung.com/java-asynchronous-programming)
- [Java Thread Deadlock and Livelock](https://www.baeldung.com/java-deadlock-livelock)
- [Guide to AtomicStampedReference in Java](https://www.baeldung.com/java-atomicstampedreference)
+- [The ABA Problem in Concurrency](https://www.baeldung.com/cs/aba-concurrency)
- [[<-- previous]](/core-java-modules/core-java-concurrency-advanced-2)
diff --git a/core-java-modules/core-java-concurrency-advanced-3/pom.xml b/core-java-modules/core-java-concurrency-advanced-3/pom.xml
index cf81214125..32267fb800 100644
--- a/core-java-modules/core-java-concurrency-advanced-3/pom.xml
+++ b/core-java-modules/core-java-concurrency-advanced-3/pom.xml
@@ -9,12 +9,11 @@
0.1.0-SNAPSHOT
core-java-concurrency-advanced-3
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-concurrency-advanced/pom.xml b/core-java-modules/core-java-concurrency-advanced/pom.xml
index d39712468f..67db486121 100644
--- a/core-java-modules/core-java-concurrency-advanced/pom.xml
+++ b/core-java-modules/core-java-concurrency-advanced/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-concurrency-advanced
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-concurrency-basic-2/pom.xml b/core-java-modules/core-java-concurrency-basic-2/pom.xml
index 8c9bbef54c..adc4fd33e3 100644
--- a/core-java-modules/core-java-concurrency-basic-2/pom.xml
+++ b/core-java-modules/core-java-concurrency-basic-2/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-concurrency-basic-2
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-concurrency-basic/pom.xml b/core-java-modules/core-java-concurrency-basic/pom.xml
index c15200da1f..29d393805b 100644
--- a/core-java-modules/core-java-concurrency-basic/pom.xml
+++ b/core-java-modules/core-java-concurrency-basic/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-concurrency-basic
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-concurrency-collections/pom.xml b/core-java-modules/core-java-concurrency-collections/pom.xml
index 5c038639a7..31f5a0fca8 100644
--- a/core-java-modules/core-java-concurrency-collections/pom.xml
+++ b/core-java-modules/core-java-concurrency-collections/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-concurrency-collections
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-date-operations-1/pom.xml b/core-java-modules/core-java-date-operations-1/pom.xml
index 54cbc79678..e12e4aa4ee 100644
--- a/core-java-modules/core-java-date-operations-1/pom.xml
+++ b/core-java-modules/core-java-date-operations-1/pom.xml
@@ -8,12 +8,11 @@
${project.parent.version}
core-java-date-operations-1
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-date-operations-2/pom.xml b/core-java-modules/core-java-date-operations-2/pom.xml
index ea5f852b0d..5861a9ab98 100644
--- a/core-java-modules/core-java-date-operations-2/pom.xml
+++ b/core-java-modules/core-java-date-operations-2/pom.xml
@@ -8,12 +8,11 @@
${project.parent.version}
core-java-date-operations-2
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-datetime-conversion/pom.xml b/core-java-modules/core-java-datetime-conversion/pom.xml
index e2dd579335..79d1394576 100644
--- a/core-java-modules/core-java-datetime-conversion/pom.xml
+++ b/core-java-modules/core-java-datetime-conversion/pom.xml
@@ -8,12 +8,11 @@
${project.parent.version}
core-java-datetime-conversion
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-datetime-string/pom.xml b/core-java-modules/core-java-datetime-string/pom.xml
index ceb7641320..c1181f670a 100644
--- a/core-java-modules/core-java-datetime-string/pom.xml
+++ b/core-java-modules/core-java-datetime-string/pom.xml
@@ -8,12 +8,11 @@
${project.parent.version}
core-java-datetime-string
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-exceptions-2/pom.xml b/core-java-modules/core-java-exceptions-2/pom.xml
index 915ec1da69..a53bf37b77 100644
--- a/core-java-modules/core-java-exceptions-2/pom.xml
+++ b/core-java-modules/core-java-exceptions-2/pom.xml
@@ -7,12 +7,11 @@
core-java-exceptions-2
core-java-exceptions-2
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-exceptions/pom.xml b/core-java-modules/core-java-exceptions/pom.xml
index 0778b6b5a3..b708aff6b9 100644
--- a/core-java-modules/core-java-exceptions/pom.xml
+++ b/core-java-modules/core-java-exceptions/pom.xml
@@ -9,12 +9,11 @@
0.1.0-SNAPSHOT
core-java-exceptions
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-function/pom.xml b/core-java-modules/core-java-function/pom.xml
index 1a853d5580..0eb34bed7b 100644
--- a/core-java-modules/core-java-function/pom.xml
+++ b/core-java-modules/core-java-function/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-function
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-io-2/pom.xml b/core-java-modules/core-java-io-2/pom.xml
index ec27c76435..bdc2ee37f5 100644
--- a/core-java-modules/core-java-io-2/pom.xml
+++ b/core-java-modules/core-java-io-2/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-io-2
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-io-apis/pom.xml b/core-java-modules/core-java-io-apis/pom.xml
index 9628027309..9350e4b527 100644
--- a/core-java-modules/core-java-io-apis/pom.xml
+++ b/core-java-modules/core-java-io-apis/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-io-apis
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-io-conversions-2/pom.xml b/core-java-modules/core-java-io-conversions-2/pom.xml
index e95d1f4b67..46bce7988b 100644
--- a/core-java-modules/core-java-io-conversions-2/pom.xml
+++ b/core-java-modules/core-java-io-conversions-2/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-io-conversions-2
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
@@ -22,6 +21,11 @@
commons-lang3
${commons-lang3.version}
+
+ org.json
+ json
+ 20200518
+
diff --git a/core-java-modules/core-java-io-conversions-2/src/test/java/com/baeldung/bufferedreadertojsonobject/JavaBufferedReaderToJSONObjectUnitTest.java b/core-java-modules/core-java-io-conversions-2/src/test/java/com/baeldung/bufferedreadertojsonobject/JavaBufferedReaderToJSONObjectUnitTest.java
new file mode 100644
index 0000000000..80007e9c2f
--- /dev/null
+++ b/core-java-modules/core-java-io-conversions-2/src/test/java/com/baeldung/bufferedreadertojsonobject/JavaBufferedReaderToJSONObjectUnitTest.java
@@ -0,0 +1,48 @@
+package com.baeldung.bufferedreadertojsonobject;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+import org.json.JSONObject;
+import org.json.JSONTokener;
+import org.junit.Test;
+
+public class JavaBufferedReaderToJSONObjectUnitTest {
+
+ @Test
+ public void givenValidJson_whenUsingBufferedReader_thenJSONTokenerConverts() {
+ byte[] b = "{ \"name\" : \"John\", \"age\" : 18 }".getBytes(StandardCharsets.UTF_8);
+ InputStream is = new ByteArrayInputStream(b);
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
+ JSONTokener tokener = new JSONTokener(bufferedReader);
+ JSONObject json = new JSONObject(tokener);
+
+ assertNotNull(json);
+ assertEquals("John", json.get("name"));
+ assertEquals(18, json.get("age"));
+ }
+
+ @Test
+ public void givenValidJson_whenUsingString_thenJSONObjectConverts() throws IOException {
+ byte[] b = "{ \"name\" : \"John\", \"age\" : 18 }".getBytes(StandardCharsets.UTF_8);
+ InputStream is = new ByteArrayInputStream(b);
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ sb.append(line);
+ }
+ JSONObject json = new JSONObject(sb.toString());
+
+ assertNotNull(json);
+ assertEquals("John", json.get("name"));
+ assertEquals(18, json.get("age"));
+ }
+}
diff --git a/core-java-modules/core-java-io-conversions/pom.xml b/core-java-modules/core-java-io-conversions/pom.xml
index f5ccaa45a3..0012b02d7e 100644
--- a/core-java-modules/core-java-io-conversions/pom.xml
+++ b/core-java-modules/core-java-io-conversions/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-io-conversions
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-io/pom.xml b/core-java-modules/core-java-io/pom.xml
index 103a809f90..ccfb57e909 100644
--- a/core-java-modules/core-java-io/pom.xml
+++ b/core-java-modules/core-java-io/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-io
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-jar/pom.xml b/core-java-modules/core-java-jar/pom.xml
index 1d87bcda5f..6e9d713d7c 100644
--- a/core-java-modules/core-java-jar/pom.xml
+++ b/core-java-modules/core-java-jar/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-jar
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-jndi/pom.xml b/core-java-modules/core-java-jndi/pom.xml
index 4a491a1a47..030a5f5d50 100644
--- a/core-java-modules/core-java-jndi/pom.xml
+++ b/core-java-modules/core-java-jndi/pom.xml
@@ -12,7 +12,7 @@
com.baeldung.core-java-modules
core-java-modules
- 1.0.0-SNAPSHOT
+ 0.0.1-SNAPSHOT
diff --git a/core-java-modules/core-java-jpms/pom.xml b/core-java-modules/core-java-jpms/pom.xml
index 4610baab49..5809c0f579 100644
--- a/core-java-modules/core-java-jpms/pom.xml
+++ b/core-java-modules/core-java-jpms/pom.xml
@@ -12,7 +12,7 @@
com.baeldung.core-java-modules
core-java-modules
- 1.0.0-SNAPSHOT
+ 0.0.1-SNAPSHOT
diff --git a/core-java-modules/core-java-jvm/pom.xml b/core-java-modules/core-java-jvm/pom.xml
index edf7a4f3c5..f3e5470a61 100644
--- a/core-java-modules/core-java-jvm/pom.xml
+++ b/core-java-modules/core-java-jvm/pom.xml
@@ -10,10 +10,10 @@
jar
- com.baeldung
- parent-modules
- 1.0.0-SNAPSHOT
- ../../
+ com.baeldung.core-java-modules
+ core-java-modules
+ 0.0.1-SNAPSHOT
+ ../
diff --git a/core-java-modules/core-java-lambdas/pom.xml b/core-java-modules/core-java-lambdas/pom.xml
index 421ca2f394..318b04fcf5 100644
--- a/core-java-modules/core-java-lambdas/pom.xml
+++ b/core-java-modules/core-java-lambdas/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-lambdas
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-lang-2/pom.xml b/core-java-modules/core-java-lang-2/pom.xml
index 5aa80ce3df..5f2d4ec901 100644
--- a/core-java-modules/core-java-lang-2/pom.xml
+++ b/core-java-modules/core-java-lang-2/pom.xml
@@ -8,19 +8,23 @@
0.1.0-SNAPSHOT
core-java-lang-2
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
org.apache.commons
commons-lang3
- 3.9
+ ${commons-lang3.version}
+
+
+ com.google.guava
+ guava
+ ${guava.version}
commons-beanutils
@@ -65,6 +69,8 @@
1.19
3.12.2
1.9.4
+ 3.10
+ 29.0-jre
diff --git a/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithEquals.java b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithEquals.java
new file mode 100644
index 0000000000..e3a61fc05a
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithEquals.java
@@ -0,0 +1,51 @@
+package com.baeldung.comparing;
+
+import java.time.LocalDate;
+import java.util.Objects;
+
+public class PersonWithEquals {
+ private String firstName;
+ private String lastName;
+ private LocalDate birthDate;
+
+ public PersonWithEquals(String firstName, String lastName) {
+ if (firstName == null || lastName == null) {
+ throw new NullPointerException("Names can't be null");
+ }
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public PersonWithEquals(String firstName, String lastName, LocalDate birthDate) {
+ this(firstName, lastName);
+
+ this.birthDate = birthDate;
+ }
+
+ public String firstName() {
+ return firstName;
+ }
+
+ public String lastName() {
+ return lastName;
+ }
+
+ public LocalDate birthDate() {
+ return birthDate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PersonWithEquals that = (PersonWithEquals) o;
+ return firstName.equals(that.firstName) &&
+ lastName.equals(that.lastName) &&
+ Objects.equals(birthDate, that.birthDate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(firstName, lastName);
+ }
+}
diff --git a/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithEqualsAndComparable.java b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithEqualsAndComparable.java
new file mode 100644
index 0000000000..5611ce8a09
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithEqualsAndComparable.java
@@ -0,0 +1,62 @@
+package com.baeldung.comparing;
+
+import java.time.LocalDate;
+import java.util.Objects;
+
+public class PersonWithEqualsAndComparable implements Comparable {
+ private String firstName;
+ private String lastName;
+ private LocalDate birthDate;
+
+ public PersonWithEqualsAndComparable(String firstName, String lastName) {
+ if (firstName == null || lastName == null) {
+ throw new NullPointerException("Names can't be null");
+ }
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public PersonWithEqualsAndComparable(String firstName, String lastName, LocalDate birthDate) {
+ this(firstName, lastName);
+
+ this.birthDate = birthDate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PersonWithEqualsAndComparable that = (PersonWithEqualsAndComparable) o;
+ return firstName.equals(that.firstName) &&
+ lastName.equals(that.lastName) &&
+ Objects.equals(birthDate, that.birthDate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(firstName, lastName);
+ }
+
+ @Override
+ public int compareTo(PersonWithEqualsAndComparable o) {
+ int lastNamesComparison = this.lastName.compareTo(o.lastName);
+ if (lastNamesComparison == 0) {
+ int firstNamesComparison = this.firstName.compareTo(o.firstName);
+ if (firstNamesComparison == 0) {
+ if (this.birthDate != null && o.birthDate != null) {
+ return this.birthDate.compareTo(o.birthDate);
+ } else if (this.birthDate != null) {
+ return 1;
+ } else if (o.birthDate != null) {
+ return -1;
+ } else {
+ return 0;
+ }
+ } else {
+ return firstNamesComparison;
+ }
+ } else {
+ return lastNamesComparison;
+ }
+ }
+}
diff --git a/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithEqualsAndComparableUsingComparator.java b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithEqualsAndComparableUsingComparator.java
new file mode 100644
index 0000000000..ed322cb353
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithEqualsAndComparableUsingComparator.java
@@ -0,0 +1,60 @@
+package com.baeldung.comparing;
+
+import java.time.LocalDate;
+import java.util.Comparator;
+import java.util.Objects;
+
+public class PersonWithEqualsAndComparableUsingComparator implements Comparable {
+ private String firstName;
+ private String lastName;
+ private LocalDate birthDate;
+
+ public PersonWithEqualsAndComparableUsingComparator(String firstName, String lastName) {
+ if (firstName == null || lastName == null) {
+ throw new NullPointerException("Names can't be null");
+ }
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public PersonWithEqualsAndComparableUsingComparator(String firstName, String lastName, LocalDate birthDate) {
+ this(firstName, lastName);
+
+ this.birthDate = birthDate;
+ }
+
+ public String firstName() {
+ return firstName;
+ }
+
+ public String lastName() {
+ return lastName;
+ }
+
+ public LocalDate birthDate() {
+ return birthDate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PersonWithEqualsAndComparableUsingComparator that = (PersonWithEqualsAndComparableUsingComparator) o;
+ return firstName.equals(that.firstName) &&
+ lastName.equals(that.lastName) &&
+ Objects.equals(birthDate, that.birthDate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(firstName, lastName);
+ }
+
+ @Override
+ public int compareTo(PersonWithEqualsAndComparableUsingComparator o) {
+ return Comparator.comparing(PersonWithEqualsAndComparableUsingComparator::lastName)
+ .thenComparing(PersonWithEqualsAndComparableUsingComparator::firstName)
+ .thenComparing(PersonWithEqualsAndComparableUsingComparator::birthDate, Comparator.nullsLast(Comparator.naturalOrder()))
+ .compare(this, o);
+ }
+}
diff --git a/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithEqualsAndWrongComparable.java b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithEqualsAndWrongComparable.java
new file mode 100644
index 0000000000..e0bdaa413a
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithEqualsAndWrongComparable.java
@@ -0,0 +1,44 @@
+package com.baeldung.comparing;
+
+import java.time.LocalDate;
+import java.util.Objects;
+
+public class PersonWithEqualsAndWrongComparable implements Comparable {
+ private String firstName;
+ private String lastName;
+ private LocalDate birthDate;
+
+ public PersonWithEqualsAndWrongComparable(String firstName, String lastName) {
+ if (firstName == null || lastName == null) {
+ throw new NullPointerException("Names can't be null");
+ }
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public PersonWithEqualsAndWrongComparable(String firstName, String lastName, LocalDate birthDate) {
+ this(firstName, lastName);
+
+ this.birthDate = birthDate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PersonWithEqualsAndWrongComparable that = (PersonWithEqualsAndWrongComparable) o;
+ return firstName.equals(that.firstName) &&
+ lastName.equals(that.lastName) &&
+ Objects.equals(birthDate, that.birthDate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(firstName, lastName);
+ }
+
+ @Override
+ public int compareTo(PersonWithEqualsAndWrongComparable o) {
+ return this.lastName.compareTo(o.lastName);
+ }
+}
diff --git a/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithoutEquals.java b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithoutEquals.java
new file mode 100644
index 0000000000..bb4c6b958b
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/comparing/PersonWithoutEquals.java
@@ -0,0 +1,11 @@
+package com.baeldung.comparing;
+
+public class PersonWithoutEquals {
+ private String firstName;
+ private String lastName;
+
+ public PersonWithoutEquals(String firstName, String lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+}
diff --git a/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/inttoenum/PizzaStatus.java b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/inttoenum/PizzaStatus.java
new file mode 100644
index 0000000000..8d7c626521
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/inttoenum/PizzaStatus.java
@@ -0,0 +1,36 @@
+package com.baeldung.inttoenum;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum PizzaStatus {
+ ORDERED(5),
+ READY(2),
+ DELIVERED(0);
+
+ private int timeToDelivery;
+
+ PizzaStatus(int timeToDelivery) {
+ this.timeToDelivery = timeToDelivery;
+ }
+
+ public int getTimeToDelivery() {
+ return timeToDelivery;
+ }
+
+ private static Map timeToDeliveryToEnumValuesMapping = new HashMap<>();
+
+ static {
+ PizzaStatus[] pizzaStatuses = PizzaStatus.values();
+ for (int pizzaStatusIndex = 0; pizzaStatusIndex < pizzaStatuses.length; pizzaStatusIndex++) {
+ timeToDeliveryToEnumValuesMapping.put(
+ pizzaStatuses[pizzaStatusIndex].getTimeToDelivery(),
+ pizzaStatuses[pizzaStatusIndex]
+ );
+ }
+ }
+
+ public static PizzaStatus castIntToEnum(int timeToDelivery) {
+ return timeToDeliveryToEnumValuesMapping.get(timeToDelivery);
+ }
+}
\ No newline at end of file
diff --git a/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/ApacheCommonsObjectUtilsUnitTest.java b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/ApacheCommonsObjectUtilsUnitTest.java
new file mode 100644
index 0000000000..33b8dcb3fc
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/ApacheCommonsObjectUtilsUnitTest.java
@@ -0,0 +1,59 @@
+package com.baeldung.comparing;
+
+import org.apache.commons.lang3.ObjectUtils;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ApacheCommonsObjectUtilsUnitTest {
+
+ @Test
+ void givenTwoStringsWithSameValues_whenApacheCommonsEqualityMethods_thenEqualsTrueNotEqualsFalse() {
+ String a = new String("Hello!");
+ String b = new String("Hello!");
+
+ assertThat(ObjectUtils.equals(a, b)).isTrue();
+ assertThat(ObjectUtils.notEqual(a, b)).isFalse();
+ }
+
+ @Test
+ void givenTwoStringsWithDifferentValues_whenApacheCommonsEqualityMethods_thenEqualsFalseNotEqualsTrue() {
+ String a = new String("Hello!");
+ String b = new String("Hello World!");
+
+ assertThat(ObjectUtils.equals(a, b)).isFalse();
+ assertThat(ObjectUtils.notEqual(a, b)).isTrue();
+ }
+
+ @Test
+ void givenTwoStringsWithConsecutiveValues_whenApacheCommonsCompare_thenNegative() {
+ String first = new String("Hello!");
+ String second = new String("How are you?");
+
+ assertThat(ObjectUtils.compare(first, second)).isNegative();
+ }
+
+ @Test
+ void givenTwoStringsWithSameValues_whenApacheCommonsEqualityMethods_thenEqualsFalseNotEqualsTrue() {
+ String first = new String("Hello!");
+ String second = new String("Hello!");
+
+ assertThat(ObjectUtils.compare(first, second)).isZero();
+ }
+
+ @Test
+ void givenTwoStringsWithConsecutiveValues_whenApacheCommonsCompareReversed_thenPositive() {
+ String first = new String("Hello!");
+ String second = new String("How are you?");
+
+ assertThat(ObjectUtils.compare(second, first)).isPositive();
+ }
+
+ @Test
+ void givenTwoStringsOneNull_whenApacheCommonsCompare_thenPositive() {
+ String first = new String("Hello!");
+ String second = null;
+
+ assertThat(ObjectUtils.compare(first, second, false)).isPositive();
+ }
+}
diff --git a/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/ComparableInterfaceUnitTest.java b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/ComparableInterfaceUnitTest.java
new file mode 100644
index 0000000000..281c4a0201
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/ComparableInterfaceUnitTest.java
@@ -0,0 +1,107 @@
+package com.baeldung.comparing;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ComparableInterfaceUnitTest {
+
+ @Test
+ void givenTwoConsecutiveStrings_whenCompareTo_thenNegative() {
+ String first = "Google";
+ String second = "Microsoft";
+
+ assertThat(first.compareTo(second)).isNegative();
+ }
+
+ @Test
+ void givenTwoEqualsStrings_whenCompareTo_thenZero() {
+ String first = "Google";
+ String second = "Google";
+
+ assertThat(first.compareTo(second)).isZero();
+ }
+
+ @Test
+ void givenTwoConsecutiveStrings_whenReversedCompareTo_thenPositive() {
+ String first = "Google";
+ String second = "Microsoft";
+
+ assertThat(second.compareTo(first)).isPositive();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsAndWrongComparableAndConsecutiveLastNames_whenCompareTo_thenNegative() {
+ PersonWithEqualsAndWrongComparable richard = new PersonWithEqualsAndWrongComparable("Richard", "Jefferson");
+ PersonWithEqualsAndWrongComparable joe = new PersonWithEqualsAndWrongComparable("Joe", "Portman");
+
+ assertThat(richard.compareTo(joe)).isNegative();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsAndWrongComparableAndSameLastNames_whenReversedCompareTo_thenZero() {
+ PersonWithEqualsAndWrongComparable richard = new PersonWithEqualsAndWrongComparable("Richard", "Jefferson");
+ PersonWithEqualsAndWrongComparable mike = new PersonWithEqualsAndWrongComparable("Mike", "Jefferson");
+
+ assertThat(richard.compareTo(mike)).isZero();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsAndWrongComparableAndConsecutiveLastNames_whenReversedCompareTo_thenPositive() {
+ PersonWithEqualsAndWrongComparable richard = new PersonWithEqualsAndWrongComparable("Richard", "Jefferson");
+ PersonWithEqualsAndWrongComparable joe = new PersonWithEqualsAndWrongComparable("Joe", "Portman");
+
+ assertThat(joe.compareTo(richard)).isPositive();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsAndWrongComparableAndSameLastNames_whenSortedSet_thenProblem() {
+ PersonWithEqualsAndWrongComparable richard = new PersonWithEqualsAndWrongComparable("Richard", "Jefferson");
+ PersonWithEqualsAndWrongComparable mike = new PersonWithEqualsAndWrongComparable("Mike", "Jefferson");
+
+ SortedSet people = new TreeSet<>();
+ people.add(richard);
+ people.add(mike);
+
+ assertThat(people).containsExactly(richard);
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsAndComparableAndConsecutiveLastNames_whenCompareTo_thenNegative() {
+ PersonWithEqualsAndComparable richard = new PersonWithEqualsAndComparable("Richard", "Jefferson");
+ PersonWithEqualsAndComparable joe = new PersonWithEqualsAndComparable("Joe", "Portman");
+
+ assertThat(richard.compareTo(joe)).isNegative();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsAndComparableAndSameLastNames_whenReversedCompareTo_thenZero() {
+ PersonWithEqualsAndComparable richard = new PersonWithEqualsAndComparable("Richard", "Jefferson");
+ PersonWithEqualsAndComparable mike = new PersonWithEqualsAndComparable("Mike", "Jefferson");
+
+ assertThat(richard.compareTo(mike)).isPositive();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsAndComparableAndConsecutiveLastNames_whenReversedCompareTo_thenPositive() {
+ PersonWithEqualsAndComparable richard = new PersonWithEqualsAndComparable("Richard", "Jefferson");
+ PersonWithEqualsAndComparable joe = new PersonWithEqualsAndComparable("Joe", "Portman");
+
+ assertThat(joe.compareTo(richard)).isPositive();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsAndComparableAndSameLastNames_whenSortedSet_thenProblem() {
+ PersonWithEqualsAndComparable richard = new PersonWithEqualsAndComparable("Richard", "Jefferson");
+ PersonWithEqualsAndComparable mike = new PersonWithEqualsAndComparable("Mike", "Jefferson");
+
+ SortedSet people = new TreeSet<>();
+ people.add(richard);
+ people.add(mike);
+
+ assertThat(people).containsExactly(mike, richard);
+ }
+}
diff --git a/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/ComparatorInterfaceUnitTest.java b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/ComparatorInterfaceUnitTest.java
new file mode 100644
index 0000000000..769ae60bed
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/ComparatorInterfaceUnitTest.java
@@ -0,0 +1,81 @@
+package com.baeldung.comparing;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.*;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ComparatorInterfaceUnitTest {
+
+ @Test
+ void givenListOfTwoPersonWithEqualsAndComparatorByFirstName_whenSort_thenSortedByFirstNames() {
+ PersonWithEquals joe = new PersonWithEquals("Joe", "Portman");
+ PersonWithEquals allan = new PersonWithEquals("Allan", "Dale");
+
+ List people = new ArrayList<>();
+ people.add(joe);
+ people.add(allan);
+
+ Comparator compareByFirstNames = new Comparator() {
+ @Override
+ public int compare(PersonWithEquals o1, PersonWithEquals o2) {
+ return o1.firstName().compareTo(o2.firstName());
+ }
+ };
+ people.sort(compareByFirstNames);
+
+ assertThat(people).containsExactly(allan, joe);
+ }
+
+ @Test
+ void givenListOfTwoPersonWithEqualsAndComparatorByFirstNameFunctionalStyle_whenSort_thenSortedByFirstNames() {
+ PersonWithEquals joe = new PersonWithEquals("Joe", "Portman");
+ PersonWithEquals allan = new PersonWithEquals("Allan", "Dale");
+
+ List people = new ArrayList<>();
+ people.add(joe);
+ people.add(allan);
+
+ Comparator compareByFirstNames = Comparator.comparing(PersonWithEquals::firstName);
+ people.sort(compareByFirstNames);
+
+ assertThat(people).containsExactly(allan, joe);
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsAndComparableUsingComparatorAndConsecutiveLastNames_whenCompareTo_thenNegative() {
+ PersonWithEqualsAndComparableUsingComparator richard = new PersonWithEqualsAndComparableUsingComparator("Richard", "Jefferson");
+ PersonWithEqualsAndComparableUsingComparator joe = new PersonWithEqualsAndComparableUsingComparator("Joe", "Portman");
+
+ assertThat(richard.compareTo(joe)).isNegative();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsAndComparableUsingComparatorAndSameLastNames_whenReversedCompareTo_thenZero() {
+ PersonWithEqualsAndComparableUsingComparator richard = new PersonWithEqualsAndComparableUsingComparator("Richard", "Jefferson");
+ PersonWithEqualsAndComparableUsingComparator mike = new PersonWithEqualsAndComparableUsingComparator("Mike", "Jefferson");
+
+ assertThat(richard.compareTo(mike)).isPositive();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsAndComparableUsingComparatorAndConsecutiveLastNames_whenReversedCompareTo_thenPositive() {
+ PersonWithEqualsAndComparableUsingComparator richard = new PersonWithEqualsAndComparableUsingComparator("Richard", "Jefferson");
+ PersonWithEqualsAndComparableUsingComparator joe = new PersonWithEqualsAndComparableUsingComparator("Joe", "Portman");
+
+ assertThat(joe.compareTo(richard)).isPositive();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsAndComparableUsingComparatorAndSameLastNames_whenSortedSet_thenProblem() {
+ PersonWithEqualsAndComparableUsingComparator richard = new PersonWithEqualsAndComparableUsingComparator("Richard", "Jefferson");
+ PersonWithEqualsAndComparableUsingComparator mike = new PersonWithEqualsAndComparableUsingComparator("Mike", "Jefferson");
+
+ SortedSet people = new TreeSet<>();
+ people.add(richard);
+ people.add(mike);
+
+ assertThat(people).containsExactly(mike, richard);
+ }
+}
diff --git a/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/EqualityOperatorUnitTest.java b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/EqualityOperatorUnitTest.java
new file mode 100644
index 0000000000..ebcf83ef5b
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/EqualityOperatorUnitTest.java
@@ -0,0 +1,116 @@
+package com.baeldung.comparing;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class EqualityOperatorUnitTest {
+
+ @Test
+ void givenTwoIntsWithSameValues_whenEqualityOperators_thenConsideredSame() {
+ int a = 1;
+ int b = 1;
+
+ assertThat(a == b).isTrue();
+ assertThat(a != b).isFalse();
+ }
+
+ @Test
+ void givenTwoIntsWithDifferentValues_whenEqualityOperators_thenNotConsideredSame() {
+ int a = 1;
+ int b = 2;
+
+ assertThat(a == b).isFalse();
+ assertThat(a != b).isTrue();
+ }
+
+ @Test
+ void givenTwoIntsWithSameValuesOneWrapped_whenEqualityOperators_thenConsideredSame() {
+ int a = 1;
+ Integer b = new Integer(1);
+
+ assertThat(a == b).isTrue();
+ assertThat(a != b).isFalse();
+ }
+
+ @Test
+ void givenTwoIntsWithDifferentValuesOneWrapped_whenEqualityOperators_thenNotConsideredSame() {
+ int a = 1;
+ Integer b = new Integer(2);
+
+ assertThat(a == b).isFalse();
+ assertThat(a != b).isTrue();
+ }
+
+ @Test
+ void givenTwoIntegersWithSameValues_whenEqualityOperators_thenNotConsideredSame() {
+ Integer a = new Integer(1);
+ Integer b = new Integer(1);
+
+ assertThat(a == b).isFalse();
+ assertThat(a != b).isTrue();
+ }
+
+ @Test
+ void givenTwoIntegersWithDifferentValues_whenEqualityOperators_thenNotConsideredSame() {
+ Integer a = new Integer(1);
+ Integer b = new Integer(2);
+
+ assertThat(a == b).isFalse();
+ assertThat(a != b).isTrue();
+ }
+
+ @Test
+ void givenTwoIntegersWithSameReference_whenEqualityOperators_thenConsideredSame() {
+ Integer a = new Integer(1);
+ Integer b = a;
+
+ assertThat(a == b).isTrue();
+ assertThat(a != b).isFalse();
+ }
+
+ @Test
+ void givenTwoIntegersFromValueOfWithSameValues_whenEqualityOperators_thenConsideredSame() {
+ Integer a = Integer.valueOf(1);
+ Integer b = Integer.valueOf(1);
+
+ assertThat(a == b).isTrue();
+ assertThat(a != b).isFalse();
+ }
+
+ @Test
+ void givenTwoStringsWithSameValues_whenEqualityOperators_thenNotConsideredSame() {
+ String a = new String("Hello!");
+ String b = new String("Hello!");
+
+ assertThat(a == b).isFalse();
+ assertThat(a != b).isTrue();
+ }
+
+ @Test
+ void givenTwoStringsFromLiteralsWithSameValues_whenEqualityOperators_thenConsideredSame() {
+ String a = "Hello!";
+ String b = "Hello!";
+
+ assertThat(a == b).isTrue();
+ assertThat(a != b).isFalse();
+ }
+
+ @Test
+ void givenTwoNullObjects_whenEqualityOperators_thenConsideredSame() {
+ Object a = null;
+ Object b = null;
+
+ assertThat(a == b).isTrue();
+ assertThat(a != b).isFalse();
+ }
+
+ @Test
+ void givenTwoObjectsOneNull_whenEqualityOperators_thenNotConsideredSame() {
+ Object a = null;
+ Object b = "Hello!";
+
+ assertThat(a == b).isFalse();
+ assertThat(a != b).isTrue();
+ }
+}
diff --git a/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/EqualsMethodUnitTest.java b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/EqualsMethodUnitTest.java
new file mode 100644
index 0000000000..a69ac38916
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/EqualsMethodUnitTest.java
@@ -0,0 +1,73 @@
+package com.baeldung.comparing;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class EqualsMethodUnitTest {
+
+ @Test
+ void givenTwoIntegersWithSameValue_whenEquals_thenTrue() {
+ Integer a = new Integer(1);
+ Integer b = new Integer(1);
+
+ assertThat(a.equals(b)).isTrue();
+ }
+
+ @Test
+ void givenTwoStringsWithSameValue_whenEquals_thenTrue() {
+ String a = new String("Hello!");
+ String b = new String("Hello!");
+
+ assertThat(a.equals(b)).isTrue();
+ }
+
+ @Test
+ void givenTwoStringsWithDifferentValue_whenEquals_thenFalse() {
+ String a = new String("Hello!");
+ String b = new String("Hello World!");
+
+ assertThat(a.equals(b)).isFalse();
+ }
+
+ @Test
+ void givenTwoObjectsFirstNull_whenEquals_thenNullPointerExceptionThrown() {
+ Object a = null;
+ Object b = new String("Hello!");
+
+ assertThrows(NullPointerException.class, () -> a.equals(b));
+ }
+
+ @Test
+ void givenTwoObjectsSecondNull_whenEquals_thenFalse() {
+ Object a = new String("Hello!");
+ Object b = null;
+
+ assertThat(a.equals(b)).isFalse();
+ }
+
+ @Test
+ void givenTwoPersonWithoutEqualsWithSameNames_whenEquals_thenFalse() {
+ PersonWithoutEquals joe = new PersonWithoutEquals("Joe", "Portman");
+ PersonWithoutEquals joeAgain = new PersonWithoutEquals("Joe", "Portman");
+
+ assertThat(joe.equals(joeAgain)).isFalse();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsWithSameNames_whenEquals_thenTrue() {
+ PersonWithEquals joe = new PersonWithEquals("Joe", "Portman");
+ PersonWithEquals joeAgain = new PersonWithEquals("Joe", "Portman");
+
+ assertThat(joe.equals(joeAgain)).isTrue();
+ }
+
+ @Test
+ void givenTwoPersonWittEqualsWithDifferentNames_whenEquals_thenFalse() {
+ PersonWithEquals joe = new PersonWithEquals("Joe", "Portman");
+ PersonWithEquals natalie = new PersonWithEquals("Natalie", "Portman");
+
+ assertThat(joe.equals(natalie)).isFalse();
+ }
+}
diff --git a/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/GuavaUnitTest.java b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/GuavaUnitTest.java
new file mode 100644
index 0000000000..5c8591e134
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/GuavaUnitTest.java
@@ -0,0 +1,73 @@
+package com.baeldung.comparing;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.primitives.Ints;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class GuavaUnitTest {
+
+ @Nested
+ class ObjectsEqualMethod {
+ @Test
+ void givenTwoStringsWithSameValues_whenObjectsEqualMethods_thenTrue() {
+ String a = new String("Hello!");
+ String b = new String("Hello!");
+
+ assertThat(Objects.equal(a, b)).isTrue();
+ }
+
+ @Test
+ void givenTwoStringsWithDifferentValues_whenObjectsEqualMethods_thenFalse() {
+ String a = new String("Hello!");
+ String b = new String("Hello World!");
+
+ assertThat(Objects.equal(a, b)).isFalse();
+ }
+ }
+
+ @Nested
+ class ComparisonMethods {
+ @Test
+ void givenTwoIntsWithConsecutiveValues_whenIntsCompareMethods_thenNegative() {
+ int first = 1;
+ int second = 2;
+ assertThat(Ints.compare(first, second)).isNegative();
+ }
+
+ @Test
+ void givenTwoIntsWithSameValues_whenIntsCompareMethods_thenZero() {
+ int first = 1;
+ int second = 1;
+
+ assertThat(Ints.compare(first, second)).isZero();
+ }
+
+ @Test
+ void givenTwoIntsWithConsecutiveValues_whenIntsCompareMethodsReversed_thenNegative() {
+ int first = 1;
+ int second = 2;
+
+ assertThat(Ints.compare(second, first)).isPositive();
+ }
+ }
+
+ @Nested
+ class ComparisonChainClass {
+ @Test
+ void givenTwoPersonWithEquals_whenComparisonChainByLastNameThenFirstName_thenSortedJoeFirstAndNatalieSecond() {
+ PersonWithEquals natalie = new PersonWithEquals("Natalie", "Portman");
+ PersonWithEquals joe = new PersonWithEquals("Joe", "Portman");
+
+ int comparisonResult = ComparisonChain.start()
+ .compare(natalie.lastName(), joe.lastName())
+ .compare(natalie.firstName(), joe.firstName())
+ .result();
+
+ assertThat(comparisonResult).isPositive();
+ }
+ }
+}
diff --git a/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/ObjectsEqualsStaticMethodUnitTest.java b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/ObjectsEqualsStaticMethodUnitTest.java
new file mode 100644
index 0000000000..5ac89da2be
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/comparing/ObjectsEqualsStaticMethodUnitTest.java
@@ -0,0 +1,50 @@
+package com.baeldung.comparing;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Objects;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ObjectsEqualsStaticMethodUnitTest {
+
+ @Test
+ void givenTwoPersonWithEqualsWithSameNames_whenObjectsEquals_thenTrue() {
+ PersonWithEquals joe = new PersonWithEquals("Joe", "Portman");
+ PersonWithEquals joeAgain = new PersonWithEquals("Joe", "Portman");
+
+ assertThat(Objects.equals(joe, joeAgain)).isTrue();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsWithDifferentNames_whenObjectsEquals_thenFalse() {
+ PersonWithEquals joe = new PersonWithEquals("Joe", "Portman");
+ PersonWithEquals natalie = new PersonWithEquals("Natalie", "Portman");
+
+ assertThat(Objects.equals(joe, natalie)).isFalse();
+ }
+
+ @Test
+ void givenTwoPersonWithEqualsFirstNull_whenObjectsEquals_thenFalse() {
+ PersonWithEquals nobody = null;
+ PersonWithEquals joe = new PersonWithEquals("Joe", "Portman");
+
+ assertThat(Objects.equals(nobody, joe)).isFalse();
+ }
+
+ @Test
+ void givenTwoObjectsSecondtNull_whenObjectsEquals_thenFalse() {
+ PersonWithEquals joe = new PersonWithEquals("Joe", "Portman");
+ PersonWithEquals nobody = null;
+
+ assertThat(Objects.equals(joe, nobody)).isFalse();
+ }
+
+ @Test
+ void givenTwoObjectsNull_whenObjectsEquals_thenTrue() {
+ PersonWithEquals nobody = null;
+ PersonWithEquals nobodyAgain = null;
+
+ assertThat(Objects.equals(nobody, nobodyAgain)).isTrue();
+ }
+}
diff --git a/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/inttoenum/IntToEnumUnitTest.java b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/inttoenum/IntToEnumUnitTest.java
new file mode 100644
index 0000000000..876c230827
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/inttoenum/IntToEnumUnitTest.java
@@ -0,0 +1,27 @@
+package com.baeldung.inttoenum;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class IntToEnumUnitTest {
+
+ @Test
+ public void whenIntToEnumUsingValuesMethod_thenReturnEnumObject() {
+ int timeToDeliveryForOrderedPizzaStatus = 5;
+ PizzaStatus[] pizzaStatuses = PizzaStatus.values();
+ PizzaStatus pizzaOrderedStatus = null;
+ for (int pizzaStatusIndex = 0; pizzaStatusIndex < pizzaStatuses.length; pizzaStatusIndex++) {
+ if (pizzaStatuses[pizzaStatusIndex].getTimeToDelivery() == timeToDeliveryForOrderedPizzaStatus) {
+ pizzaOrderedStatus = pizzaStatuses[pizzaStatusIndex];
+ }
+ }
+ assertEquals(pizzaOrderedStatus, PizzaStatus.ORDERED);
+ }
+
+ @Test
+ public void whenIntToEnumUsingMap_thenReturnEnumObject() {
+ int timeToDeliveryForOrderedPizzaStatus = 5;
+ assertEquals(PizzaStatus.castIntToEnum(timeToDeliveryForOrderedPizzaStatus), PizzaStatus.ORDERED);
+ }
+}
\ No newline at end of file
diff --git a/core-java-modules/core-java-lang-math-2/pom.xml b/core-java-modules/core-java-lang-math-2/pom.xml
index 92ebcc6a94..e2cced4fbf 100644
--- a/core-java-modules/core-java-lang-math-2/pom.xml
+++ b/core-java-modules/core-java-lang-math-2/pom.xml
@@ -5,12 +5,11 @@
core-java-lang-math-2
0.0.1-SNAPSHOT
core-java-lang-math-2
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-lang-math/pom.xml b/core-java-modules/core-java-lang-math/pom.xml
index bcb5cf39d2..81ff0d43ea 100644
--- a/core-java-modules/core-java-lang-math/pom.xml
+++ b/core-java-modules/core-java-lang-math/pom.xml
@@ -8,12 +8,11 @@
0.1.0-SNAPSHOT
core-java-lang-math
jar
-
- com.baeldung
- parent-java
+ com.baeldung.core-java-modules
+ core-java-modules
0.0.1-SNAPSHOT
- ../../parent-java
+ ../
diff --git a/core-java-modules/core-java-lang-oop-constructors/pom.xml b/core-java-modules/core-java-lang-oop-constructors/pom.xml
index 76507103ea..e54286a822 100644
--- a/core-java-modules/core-java-lang-oop-constructors/pom.xml
+++ b/core-java-modules/core-java-lang-oop-constructors/pom.xml
@@ -5,7 +5,7 @@
core-java-modules
com.baeldung.core-java-modules
- 1.0.0-SNAPSHOT
+ 0.0.1-SNAPSHOT
4.0.0
diff --git a/core-java-modules/core-java-lang-oop-generics/pom.xml b/core-java-modules/core-java-lang-oop-generics/pom.xml
index ae141ecda2..65a0aeac59 100644
--- a/core-java-modules/core-java-lang-oop-generics/pom.xml
+++ b/core-java-modules/core-java-lang-oop-generics/pom.xml
@@ -5,7 +5,7 @@
core-java-modules
com.baeldung.core-java-modules
- 1.0.0-SNAPSHOT
+ 0.0.1-SNAPSHOT
4.0.0
diff --git a/core-java-modules/core-java-lang-oop-generics/src/main/java/com/baeldung/supertype/TypeReference.java b/core-java-modules/core-java-lang-oop-generics/src/main/java/com/baeldung/supertype/TypeReference.java
new file mode 100644
index 0000000000..2021f42239
--- /dev/null
+++ b/core-java-modules/core-java-lang-oop-generics/src/main/java/com/baeldung/supertype/TypeReference.java
@@ -0,0 +1,18 @@
+package com.baeldung.supertype;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+public abstract class TypeReference {
+
+ private final Type type;
+
+ public TypeReference() {
+ Type superclass = getClass().getGenericSuperclass();
+ type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
+ }
+
+ public Type getType() {
+ return type;
+ }
+}
diff --git a/core-java-modules/core-java-lang-oop-generics/src/test/java/com/baeldung/supertype/TypeReferenceUnitTest.java b/core-java-modules/core-java-lang-oop-generics/src/test/java/com/baeldung/supertype/TypeReferenceUnitTest.java
new file mode 100644
index 0000000000..24e3b698e2
--- /dev/null
+++ b/core-java-modules/core-java-lang-oop-generics/src/test/java/com/baeldung/supertype/TypeReferenceUnitTest.java
@@ -0,0 +1,24 @@
+package com.baeldung.supertype;
+
+import org.junit.Test;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+public class TypeReferenceUnitTest {
+
+ @Test
+ public void givenGenericToken_whenUsingSuperTypeToken_thenPreservesTheTypeInfo() {
+ TypeReference
+
+ org.apache.kafka
+ kafka-clients
+ ${kafka.version}
+
org.awaitility
awaitility
@@ -184,6 +189,7 @@
RELEASE
3.0
1.8.1
+ 2.5.0
diff --git a/libraries-data-2/src/main/java/com/baeldung/kafka/consumer/CountryPopulation.java b/libraries-data-2/src/main/java/com/baeldung/kafka/consumer/CountryPopulation.java
new file mode 100644
index 0000000000..8c1351642f
--- /dev/null
+++ b/libraries-data-2/src/main/java/com/baeldung/kafka/consumer/CountryPopulation.java
@@ -0,0 +1,28 @@
+package com.baeldung.kafka.consumer;
+
+class CountryPopulation {
+
+ private String country;
+ private Integer population;
+
+ public CountryPopulation(String country, Integer population) {
+ this.country = country;
+ this.population = population;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public Integer getPopulation() {
+ return population;
+ }
+
+ public void setPopulation(Integer population) {
+ this.population = population;
+ }
+}
\ No newline at end of file
diff --git a/libraries-data-2/src/main/java/com/baeldung/kafka/consumer/CountryPopulationConsumer.java b/libraries-data-2/src/main/java/com/baeldung/kafka/consumer/CountryPopulationConsumer.java
new file mode 100644
index 0000000000..ba4dfe6f3b
--- /dev/null
+++ b/libraries-data-2/src/main/java/com/baeldung/kafka/consumer/CountryPopulationConsumer.java
@@ -0,0 +1,60 @@
+package com.baeldung.kafka.consumer;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.stream.StreamSupport;
+
+import org.apache.kafka.clients.consumer.Consumer;
+import org.apache.kafka.clients.consumer.ConsumerRecords;
+import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.errors.WakeupException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CountryPopulationConsumer {
+
+ private static Logger logger = LoggerFactory.getLogger(CountryPopulationConsumer.class);
+
+ private Consumer consumer;
+ private java.util.function.Consumer exceptionConsumer;
+ private java.util.function.Consumer countryPopulationConsumer;
+
+ public CountryPopulationConsumer(
+ Consumer consumer, java.util.function.Consumer exceptionConsumer,
+ java.util.function.Consumer countryPopulationConsumer) {
+ this.consumer = consumer;
+ this.exceptionConsumer = exceptionConsumer;
+ this.countryPopulationConsumer = countryPopulationConsumer;
+ }
+
+ void startBySubscribing(String topic) {
+ consume(() -> consumer.subscribe(Collections.singleton(topic)));
+ }
+
+ void startByAssigning(String topic, int partition) {
+ consume(() -> consumer.assign(Collections.singleton(new TopicPartition(topic, partition))));
+ }
+
+ private void consume(Runnable beforePollingTask) {
+ try {
+ beforePollingTask.run();
+ while (true) {
+ ConsumerRecords records = consumer.poll(Duration.ofMillis(1000));
+ StreamSupport.stream(records.spliterator(), false)
+ .map(record -> new CountryPopulation(record.key(), record.value()))
+ .forEach(countryPopulationConsumer);
+ consumer.commitSync();
+ }
+ } catch (WakeupException e) {
+ logger.info("Shutting down...");
+ } catch (RuntimeException ex) {
+ exceptionConsumer.accept(ex);
+ } finally {
+ consumer.close();
+ }
+ }
+
+ public void stop() {
+ consumer.wakeup();
+ }
+}
\ No newline at end of file
diff --git a/libraries-data-2/src/test/java/com/baeldung/kafka/consumer/CountryPopulationConsumerUnitTest.java b/libraries-data-2/src/test/java/com/baeldung/kafka/consumer/CountryPopulationConsumerUnitTest.java
new file mode 100644
index 0000000000..1b49c71716
--- /dev/null
+++ b/libraries-data-2/src/test/java/com/baeldung/kafka/consumer/CountryPopulationConsumerUnitTest.java
@@ -0,0 +1,100 @@
+package com.baeldung.kafka.consumer;
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.kafka.clients.consumer.MockConsumer;
+import org.apache.kafka.clients.consumer.OffsetResetStrategy;
+import org.apache.kafka.common.KafkaException;
+import org.apache.kafka.common.TopicPartition;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class CountryPopulationConsumerUnitTest {
+
+ private static final String TOPIC = "topic";
+ private static final int PARTITION = 0;
+
+ private CountryPopulationConsumer countryPopulationConsumer;
+
+ private List updates;
+ private Throwable pollException;
+
+ private MockConsumer consumer;
+
+ @BeforeEach
+ void setUp() {
+ consumer = new MockConsumer<>(OffsetResetStrategy.EARLIEST);
+ updates = new ArrayList<>();
+ countryPopulationConsumer = new CountryPopulationConsumer(consumer, ex -> this.pollException = ex, updates::add);
+ }
+
+ @Test
+ void whenStartingByAssigningTopicPartition_thenExpectUpdatesAreConsumedCorrectly() {
+ // GIVEN
+ consumer.schedulePollTask(() -> consumer.addRecord(record(TOPIC, PARTITION, "Romania", 19_410_000)));
+ consumer.schedulePollTask(() -> countryPopulationConsumer.stop());
+
+ HashMap startOffsets = new HashMap<>();
+ TopicPartition tp = new TopicPartition(TOPIC, PARTITION);
+ startOffsets.put(tp, 0L);
+ consumer.updateBeginningOffsets(startOffsets);
+
+ // WHEN
+ countryPopulationConsumer.startByAssigning(TOPIC, PARTITION);
+
+ // THEN
+ assertThat(updates).hasSize(1);
+ assertThat(consumer.closed()).isTrue();
+ }
+
+ @Test
+ void whenStartingBySubscribingToTopic_thenExpectUpdatesAreConsumedCorrectly() {
+ // GIVEN
+ consumer.schedulePollTask(() -> {
+ consumer.rebalance(Collections.singletonList(new TopicPartition(TOPIC, 0)));
+ consumer.addRecord(record(TOPIC, PARTITION, "Romania", 19_410_000));
+ });
+ consumer.schedulePollTask(() -> countryPopulationConsumer.stop());
+
+ HashMap startOffsets = new HashMap<>();
+ TopicPartition tp = new TopicPartition(TOPIC, PARTITION);
+ startOffsets.put(tp, 0L);
+ consumer.updateBeginningOffsets(startOffsets);
+
+ // WHEN
+ countryPopulationConsumer.startBySubscribing(TOPIC);
+
+ // THEN
+ assertThat(updates).hasSize(1);
+ assertThat(consumer.closed()).isTrue();
+ }
+
+ @Test
+ void whenStartingBySubscribingToTopicAndExceptionOccurs_thenExpectExceptionIsHandledCorrectly() {
+ // GIVEN
+ consumer.schedulePollTask(() -> consumer.setPollException(new KafkaException("poll exception")));
+ consumer.schedulePollTask(() -> countryPopulationConsumer.stop());
+
+ HashMap startOffsets = new HashMap<>();
+ TopicPartition tp = new TopicPartition(TOPIC, 0);
+ startOffsets.put(tp, 0L);
+ consumer.updateBeginningOffsets(startOffsets);
+
+ // WHEN
+ countryPopulationConsumer.startBySubscribing(TOPIC);
+
+ // THEN
+ assertThat(pollException).isInstanceOf(KafkaException.class).hasMessage("poll exception");
+ assertThat(consumer.closed()).isTrue();
+ }
+
+ private ConsumerRecord record(String topic, int partition, String country, int population) {
+ return new ConsumerRecord<>(topic, partition, 0, country, population);
+ }
+}
\ No newline at end of file
diff --git a/libraries-rpc/README.md b/libraries-rpc/README.md
new file mode 100644
index 0000000000..472aa883ad
--- /dev/null
+++ b/libraries-rpc/README.md
@@ -0,0 +1,3 @@
+### Relevant Articles:
+
+- [Introduction to Finagle](https://www.baeldung.com/java-finagle)
diff --git a/libraries-testing/pom.xml b/libraries-testing/pom.xml
index ad6c81a3d6..89cb0bd494 100644
--- a/libraries-testing/pom.xml
+++ b/libraries-testing/pom.xml
@@ -195,7 +195,7 @@
0.8.1
4.3.8.RELEASE
4.1.1
- 3.6.2
+ 3.14.0
2.0.0.0
1.4.200
2.7.0
diff --git a/maven-java-11/multimodule-maven-project/entitymodule/pom.xml b/maven-java-11/multimodule-maven-project/entitymodule/pom.xml
new file mode 100644
index 0000000000..228619ed74
--- /dev/null
+++ b/maven-java-11/multimodule-maven-project/entitymodule/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+ com.baeldung.entitymodule
+ entitymodule
+ 1.0
+ entitymodule
+ jar
+
+
+ com.baeldung.multimodule-maven-project
+ multimodule-maven-project
+ 1.0
+
+
+
+ 11
+ 11
+
+
+
diff --git a/maven-java-11/multimodule-maven-project/entitymodule/src/main/java/com/baeldung/entity/User.java b/maven-java-11/multimodule-maven-project/entitymodule/src/main/java/com/baeldung/entity/User.java
new file mode 100644
index 0000000000..22022a2e6d
--- /dev/null
+++ b/maven-java-11/multimodule-maven-project/entitymodule/src/main/java/com/baeldung/entity/User.java
@@ -0,0 +1,19 @@
+package com.baeldung.entity;
+
+public class User {
+
+ private final String name;
+
+ public User(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return "User{" + "name=" + name + '}';
+ }
+}
diff --git a/maven-java-11/multimodule-maven-project/entitymodule/src/main/java/module-info.java b/maven-java-11/multimodule-maven-project/entitymodule/src/main/java/module-info.java
new file mode 100644
index 0000000000..67a3097352
--- /dev/null
+++ b/maven-java-11/multimodule-maven-project/entitymodule/src/main/java/module-info.java
@@ -0,0 +1,3 @@
+module com.baeldung.entity {
+ exports com.baeldung.entity;
+}
diff --git a/maven-java-11/multimodule-maven-project/mainappmodule/pom.xml b/maven-java-11/multimodule-maven-project/mainappmodule/pom.xml
new file mode 100644
index 0000000000..1f4493c34c
--- /dev/null
+++ b/maven-java-11/multimodule-maven-project/mainappmodule/pom.xml
@@ -0,0 +1,41 @@
+
+
+ 4.0.0
+ com.baeldung.mainappmodule
+ mainappmodule
+ 1.0
+ mainappmodule
+ jar
+
+
+ com.baeldung.multimodule-maven-project
+ multimodule-maven-project
+ 1.0
+
+
+
+
+ com.baeldung.entitymodule
+ entitymodule
+ ${entitymodule.version}
+
+
+ com.baeldung.daomodule
+ daomodule
+ ${daomodule.version}
+
+
+ com.baeldung.userdaomodule
+ userdaomodule
+ ${userdaomodule.version}
+
+
+
+
+ 1.0
+ 1.0
+ 1.0
+
+
+
diff --git a/maven-java-11/multimodule-maven-project/mainappmodule/src/main/java/com/baeldung/mainapp/Application.java b/maven-java-11/multimodule-maven-project/mainappmodule/src/main/java/com/baeldung/mainapp/Application.java
new file mode 100644
index 0000000000..0c0df7461b
--- /dev/null
+++ b/maven-java-11/multimodule-maven-project/mainappmodule/src/main/java/com/baeldung/mainapp/Application.java
@@ -0,0 +1,19 @@
+package com.baeldung.mainapp;
+
+import com.baeldung.dao.Dao;
+import com.baeldung.entity.User;
+import com.baeldung.userdao.UserDao;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Application {
+
+ public static void main(String[] args) {
+ Map users = new HashMap<>();
+ users.put(1, new User("Julie"));
+ users.put(2, new User("David"));
+ Dao userDao = new UserDao(users);
+ userDao.findAll().forEach(System.out::println);
+ }
+
+}
diff --git a/maven-java-11/multimodule-maven-project/mainappmodule/src/main/java/module-info.java b/maven-java-11/multimodule-maven-project/mainappmodule/src/main/java/module-info.java
new file mode 100644
index 0000000000..c688fcf7de
--- /dev/null
+++ b/maven-java-11/multimodule-maven-project/mainappmodule/src/main/java/module-info.java
@@ -0,0 +1,6 @@
+module com.baeldung.mainapp {
+ requires com.baeldung.entity;
+ requires com.baeldung.userdao;
+ requires com.baeldung.dao;
+ uses com.baeldung.dao.Dao;
+}
diff --git a/netflix-modules/genie/README.md b/netflix-modules/genie/README.md
new file mode 100644
index 0000000000..f6e15ba403
--- /dev/null
+++ b/netflix-modules/genie/README.md
@@ -0,0 +1,3 @@
+### Relevant Articles:
+
+- [Introduction to Netflix Genie](https://www.baeldung.com/netflix-genie-intro)
diff --git a/patterns/cqrs-es/README.md b/patterns/cqrs-es/README.md
new file mode 100644
index 0000000000..92570280ab
--- /dev/null
+++ b/patterns/cqrs-es/README.md
@@ -0,0 +1,5 @@
+This module contains articles about composing together CQRS and Event Sourcing
+
+## Relevant Articles
+
+- [CQRS and Event Sourcing in Java](https://www.baeldung.com/cqrs-event-sourcing-java)
diff --git a/persistence-modules/hibernate-libraries/create-database.sh b/persistence-modules/hibernate-libraries/create-database.sh
new file mode 100755
index 0000000000..da55dc917d
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/create-database.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+docker run \
+ -p 53306:3306 \
+ --name=mysql57-hibernate-types \
+ -e MYSQL_ALLOW_EMPTY_PASSWORD=true \
+ -v "${PWD}/docker/docker-entrypoint-initdb.d":/docker-entrypoint-initdb.d \
+ -d mysql:5.7
diff --git a/persistence-modules/hibernate-libraries/docker-compose.yml b/persistence-modules/hibernate-libraries/docker-compose.yml
new file mode 100644
index 0000000000..3ea9fef2e7
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/docker-compose.yml
@@ -0,0 +1,19 @@
+version: '3.2'
+
+services:
+ mysql:
+ image: mysql:5.7
+ container_name: mysql57
+ restart: unless-stopped
+ ports:
+ - 53306:3306
+ environment:
+ - MYSQL_ALLOW_EMPTY_PASSWORD=true
+ volumes :
+ - ./docker/etc/mysql/conf.d:/etc/mysql/conf.d
+ - ./docker/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "50m"
+ max-file: "1"
diff --git a/persistence-modules/hibernate-libraries/docker/docker-entrypoint-initdb.d/init-db.sql b/persistence-modules/hibernate-libraries/docker/docker-entrypoint-initdb.d/init-db.sql
new file mode 100644
index 0000000000..1df234f5a1
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/docker/docker-entrypoint-initdb.d/init-db.sql
@@ -0,0 +1,3 @@
+CREATE DATABASE hibernate_types;
+use hibernate_types;
+GRANT ALL PRIVILEGES ON hibernate_types.* TO 'mysql'@'%' IDENTIFIED BY 'admin';
diff --git a/persistence-modules/hibernate-libraries/pom.xml b/persistence-modules/hibernate-libraries/pom.xml
new file mode 100644
index 0000000000..ea2dda7e88
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/pom.xml
@@ -0,0 +1,188 @@
+
+
+ 4.0.0
+ hibernate-libraries
+ 0.0.1-SNAPSHOT
+ hibernate-libraries
+ Introduction into hibernate types library
+
+
+ com.baeldung
+ persistence-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+ com.vladmihalcea
+ hibernate-types-52
+ ${hibernate-types.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+ ${spring-boot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ ${spring-boot.version}
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ ${spring-boot.version}
+ runtime
+ true
+
+
+ org.hibernate
+ hibernate-core
+ ${hibernate.version}
+ provided
+
+
+ org.hibernate
+ hibernate-ehcache
+ ${hibernate.version}
+ test
+
+
+ org.hibernate
+ hibernate-testing
+ ${hibernate.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj-core.version}
+ test
+
+
+ mysql
+ mysql-connector-java
+ ${mysql.version}
+ runtime
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+ provided
+ true
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+ provided
+ true
+
+
+ javax.xml.bind
+ jaxb-api
+ ${jaxb.version}
+
+
+ org.javassist
+ javassist
+ ${javassist.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+ provided
+ true
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+ provided
+ true
+
+
+ net.ttddyy
+ datasource-proxy
+ ${datasource-proxy.version}
+ test
+
+
+ com.integralblue
+ log4jdbc-spring-boot-starter
+ ${log4jdbc.version}
+
+
+
+
+ hibernate-types
+
+
+ src/main/resources
+ true
+
+
+
+
+
+
+ integration
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ integration-test
+
+ test
+
+
+
+ none
+
+
+ **/*IntegrationTest.java
+
+
+
+
+
+
+
+
+
+
+
+ 3.15.0
+ 1.6
+ 29.0-jre
+ 2.9.7
+ 5.4.14.Final
+ 2.10.3
+ 1.8
+ 3.27.0-GA
+ 2.3.1
+ 2.0.0
+ 1.2.3
+ 3.0.2
+ 2.22.2
+ 3.8.1
+ 3.8.1
+ 8.0.19
+ 1.7.30
+ 2.1.3.RELEASE
+
+
+
diff --git a/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/Album.java b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/Album.java
new file mode 100644
index 0000000000..f18818c3a9
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/Album.java
@@ -0,0 +1,34 @@
+package com.baeldung.hibernate.types;
+
+import org.hibernate.annotations.Type;
+
+import javax.persistence.*;
+import java.util.List;
+
+@Entity(name = "Album")
+@Table(name = "album")
+public class Album extends BaseEntity {
+ @Type(type = "json")
+ @Column(columnDefinition = "json")
+ private CoverArt coverArt;
+
+ @OneToMany(fetch = FetchType.EAGER)
+ private List songs;
+
+ public CoverArt getCoverArt() {
+ return coverArt;
+ }
+
+ public void setCoverArt(CoverArt coverArt) {
+ this.coverArt = coverArt;
+ }
+
+
+ public List getSongs() {
+ return songs;
+ }
+
+ public void setSong(List songs) {
+ this.songs = songs;
+ }
+}
diff --git a/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/AlbumRepository.java b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/AlbumRepository.java
new file mode 100644
index 0000000000..d89542de46
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/AlbumRepository.java
@@ -0,0 +1,8 @@
+package com.baeldung.hibernate.types;
+
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface AlbumRepository extends CrudRepository {
+}
diff --git a/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/Artist.java b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/Artist.java
new file mode 100644
index 0000000000..8f3ccb44c5
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/Artist.java
@@ -0,0 +1,72 @@
+package com.baeldung.hibernate.types;
+
+import java.io.Serializable;
+
+public class Artist implements Serializable {
+
+ private String name;
+ private String country;
+ private String genre;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public String getGenre() {
+ return genre;
+ }
+
+ public void setGenre(String genre) {
+ this.genre = genre;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((country == null) ? 0 : country.hashCode());
+ result = prime * result + ((genre == null) ? 0 : genre.hashCode());
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Artist other = (Artist) obj;
+ if (country == null) {
+ if (other.country != null)
+ return false;
+ } else if (!country.equals(other.country))
+ return false;
+ if (genre == null) {
+ if (other.genre != null)
+ return false;
+ } else if (!genre.equals(other.genre))
+ return false;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ return true;
+ }
+
+ }
diff --git a/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/BaseEntity.java b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/BaseEntity.java
new file mode 100644
index 0000000000..3e0fbc7595
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/BaseEntity.java
@@ -0,0 +1,37 @@
+package com.baeldung.hibernate.types;
+
+import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
+import com.vladmihalcea.hibernate.type.json.JsonStringType;
+import org.hibernate.annotations.TypeDef;
+import org.hibernate.annotations.TypeDefs;
+
+import javax.persistence.*;
+
+@TypeDefs({
+ @TypeDef(name = "json", typeClass = JsonStringType.class),
+ @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
+})
+@MappedSuperclass
+public class BaseEntity {
+ @Id
+ @GeneratedValue(strategy=GenerationType.AUTO)
+ @Column(name = "id", unique = true, nullable = false)
+ long id;
+ String name;
+
+ 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;
+ }
+}
diff --git a/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/CoverArt.java b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/CoverArt.java
new file mode 100644
index 0000000000..bd71edc53c
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/CoverArt.java
@@ -0,0 +1,71 @@
+package com.baeldung.hibernate.types;
+
+import java.io.Serializable;
+
+public class CoverArt implements Serializable {
+
+ private String frontCoverArtUrl;
+ private String backCoverArtUrl;
+ private String upcCode;
+
+ public String getFrontCoverArtUrl() {
+ return frontCoverArtUrl;
+ }
+
+ public void setFrontCoverArtUrl(String frontCoverArtUrl) {
+ this.frontCoverArtUrl = frontCoverArtUrl;
+ }
+
+ public String getBackCoverArtUrl() {
+ return backCoverArtUrl;
+ }
+
+ public void setBackCoverArtUrl(String backCoverArtUrl) {
+ this.backCoverArtUrl = backCoverArtUrl;
+ }
+
+ public String getUpcCode() {
+ return upcCode;
+ }
+
+ public void setUpcCode(String upcCode) {
+ this.upcCode = upcCode;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((backCoverArtUrl == null) ? 0 : backCoverArtUrl.hashCode());
+ result = prime * result + ((frontCoverArtUrl == null) ? 0 : frontCoverArtUrl.hashCode());
+ result = prime * result + ((upcCode == null) ? 0 : upcCode.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ CoverArt other = (CoverArt) obj;
+ if (backCoverArtUrl == null) {
+ if (other.backCoverArtUrl != null)
+ return false;
+ } else if (!backCoverArtUrl.equals(other.backCoverArtUrl))
+ return false;
+ if (frontCoverArtUrl == null) {
+ if (other.frontCoverArtUrl != null)
+ return false;
+ } else if (!frontCoverArtUrl.equals(other.frontCoverArtUrl))
+ return false;
+ if (upcCode == null) {
+ if (other.upcCode != null)
+ return false;
+ } else if (!upcCode.equals(other.upcCode))
+ return false;
+ return true;
+ }
+}
diff --git a/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/HibernateTypesApplication.java b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/HibernateTypesApplication.java
new file mode 100644
index 0000000000..ac379f9ee2
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/HibernateTypesApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.hibernate.types;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class HibernateTypesApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(HibernateTypesApplication.class, args);
+ }
+
+}
diff --git a/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/Song.java b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/Song.java
new file mode 100644
index 0000000000..2d22296024
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/Song.java
@@ -0,0 +1,57 @@
+package com.baeldung.hibernate.types;
+
+import org.hibernate.annotations.Type;
+import org.hibernate.annotations.TypeDef;
+
+import java.time.YearMonth;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import com.vladmihalcea.hibernate.type.basic.YearMonthIntegerType;
+
+@Entity(name = "Song")
+@Table(name = "song")
+@TypeDef(
+ typeClass = YearMonthIntegerType.class,
+ defaultForType = YearMonth.class
+)
+public class Song extends BaseEntity {
+
+ private Long length = 0L;
+
+ @Type(type = "json")
+ @Column(columnDefinition = "json")
+ private Artist artist;
+
+ @Column(
+ name = "recorded_on",
+ columnDefinition = "mediumint"
+ )
+ private YearMonth recordedOn = YearMonth.now();
+
+ public Long getLength() {
+ return length;
+ }
+
+ public void setLength(Long length) {
+ this.length = length;
+ }
+
+ public Artist getArtist() {
+ return artist;
+ }
+
+ public void setArtist(Artist artist) {
+ this.artist = artist;
+ }
+
+ public YearMonth getRecordedOn() {
+ return recordedOn;
+ }
+
+ public void setRecordedOn(YearMonth recordedOn) {
+ this.recordedOn = recordedOn;
+ }
+}
\ No newline at end of file
diff --git a/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/SongRepository.java b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/SongRepository.java
new file mode 100644
index 0000000000..e79e88108b
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/src/main/java/com/baeldung/hibernate/types/SongRepository.java
@@ -0,0 +1,8 @@
+package com.baeldung.hibernate.types;
+
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SongRepository extends CrudRepository {
+}
diff --git a/persistence-modules/hibernate-libraries/src/main/resources/application.properties b/persistence-modules/hibernate-libraries/src/main/resources/application.properties
new file mode 100644
index 0000000000..9588139eb0
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/src/main/resources/application.properties
@@ -0,0 +1,14 @@
+log4jdbc.dump.sql.addsemicolon=true
+log4jdbc.dump.sql.maxlinelength=0
+log4jdbc.trim.sql.extrablanklines=false
+logging.level.jdbc.audit=fatal
+logging.level.jdbc.connection=fatal
+logging.level.jdbc.resultset=fatal
+logging.level.jdbc.resultsettable=info
+logging.level.jdbc.sqlonly=fatal
+logging.level.jdbc.sqltiming=info
+spring.datasource.url=jdbc:mysql://localhost:53306/hibernate_types?serverTimezone=UTC&useSSL=false
+spring.datasource.username=root
+spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
+spring.jpa.hibernate.ddl-auto=create
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
diff --git a/persistence-modules/hibernate-libraries/src/main/resources/hibernate-types.properties b/persistence-modules/hibernate-libraries/src/main/resources/hibernate-types.properties
new file mode 100644
index 0000000000..226b50fafd
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/src/main/resources/hibernate-types.properties
@@ -0,0 +1 @@
+hibernate.types.print.banner=false
diff --git a/persistence-modules/hibernate-libraries/src/test/java/com/baeldung/hibernate/types/HibernateTypesLiveTest.java b/persistence-modules/hibernate-libraries/src/test/java/com/baeldung/hibernate/types/HibernateTypesLiveTest.java
new file mode 100644
index 0000000000..4b551386ad
--- /dev/null
+++ b/persistence-modules/hibernate-libraries/src/test/java/com/baeldung/hibernate/types/HibernateTypesLiveTest.java
@@ -0,0 +1,181 @@
+package com.baeldung.hibernate.types;
+
+import com.google.common.collect.Lists;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.time.Duration;
+import java.time.YearMonth;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+public class HibernateTypesLiveTest {
+
+ @Autowired
+ AlbumRepository albumRepository;
+
+ @Autowired
+ SongRepository songRepository;
+
+ private void deleteAll() {
+ albumRepository.deleteAll();
+ songRepository.deleteAll();
+ }
+
+ @BeforeEach
+ public void setUp() {
+ deleteAll();
+ }
+
+ @BeforeEach
+ public void tearDown() {
+ setUp();
+ }
+
+ @Test
+ void whenSavingHibernateTypes_thenTheCorrectJsonIsStoredInTheDatabase() {
+ Album emptyAlbum = new Album();
+ emptyAlbum = albumRepository.save(emptyAlbum);
+
+ Song emptySong = new Song();
+ emptySong = songRepository.save(emptySong);
+
+ Artist superstarArtist = new Artist();
+ superstarArtist.setCountry("England");
+ superstarArtist.setGenre("Pop");
+ superstarArtist.setName("Superstar");
+
+ Song aHappySong = new Song();
+ aHappySong.setArtist(superstarArtist);
+ aHappySong.setName("A Happy Song");
+ aHappySong.setLength(Duration.ofMinutes(4).getSeconds());
+ aHappySong = songRepository.save(aHappySong);
+
+ Song aSadSong = new Song();
+ aSadSong.setArtist(superstarArtist);
+ aSadSong.setName("A Sad Song");
+ aSadSong.setLength(Duration.ofMinutes(2).getSeconds());
+ aSadSong = songRepository.save(aSadSong);
+
+ Song anotherHappySong = new Song();
+ anotherHappySong.setArtist(superstarArtist);
+ anotherHappySong.setName("Another Happy Song");
+ anotherHappySong.setLength(Duration.ofMinutes(3).getSeconds());
+ anotherHappySong = songRepository.save(anotherHappySong);
+
+ Artist newcomer = new Artist();
+ newcomer.setCountry("Jamaica");
+ newcomer.setGenre("Reggae");
+ newcomer.setName("Newcomer");
+
+ Song aNewSong = new Song();
+ aNewSong.setArtist(newcomer);
+ aNewSong.setName("A New Song");
+ aNewSong.setLength(Duration.ofMinutes(5).getSeconds());
+ aNewSong = songRepository.save(aNewSong);
+
+ CoverArt superstarAlbumCoverArt = new CoverArt();
+ superstarAlbumCoverArt.setUpcCode(UUID.randomUUID().toString());
+ superstarAlbumCoverArt.setFrontCoverArtUrl("http://fakeurl-0");
+ superstarAlbumCoverArt.setBackCoverArtUrl("http://fakeurl-1");
+
+ Album superstarAlbum = new Album();
+ superstarAlbum.setCoverArt(superstarAlbumCoverArt);
+ superstarAlbum.setName("The Superstar Album");
+ superstarAlbum.setSong(Lists.newArrayList(aHappySong, aSadSong, anotherHappySong));
+ superstarAlbum = albumRepository.save(superstarAlbum);
+
+ CoverArt newcomerAlbumCoverArt = new CoverArt();
+ newcomerAlbumCoverArt.setUpcCode(UUID.randomUUID().toString());
+ newcomerAlbumCoverArt.setFrontCoverArtUrl("http://fakeurl-2");
+ newcomerAlbumCoverArt.setBackCoverArtUrl("http://fakeurl-3");
+
+ Album newcomerAlbum = new Album();
+ newcomerAlbum.setCoverArt(newcomerAlbumCoverArt);
+ newcomerAlbum.setName("The Newcomer Album");
+ newcomerAlbum.setSong(Lists.newArrayList(aNewSong));
+ albumRepository.save(newcomerAlbum);
+
+ Iterable selectAlbumsQueryResult = albumRepository.findAll();
+ assertThat(selectAlbumsQueryResult).hasSize(3);
+
+ Iterable selectSongsQueryResult = songRepository.findAll();
+ assertThat(selectSongsQueryResult).hasSize(5);
+
+ Album selectAlbumQueryResult;
+
+ selectAlbumQueryResult = albumRepository.findById(emptyAlbum.getId()).get();
+ assertThat(selectAlbumQueryResult.getName()).isNull();
+ assertThat(selectAlbumQueryResult.getCoverArt()).isNull();
+ assertThat(selectAlbumQueryResult.getSongs()).isNullOrEmpty();
+
+ selectAlbumQueryResult = albumRepository.findById(superstarAlbum.getId()).get();
+ assertThat(selectAlbumQueryResult.getName()).isEqualTo("The Superstar Album");
+ assertThat(selectAlbumQueryResult.getCoverArt().getFrontCoverArtUrl()).isEqualTo("http://fakeurl-0");
+ assertThat(selectAlbumQueryResult.getCoverArt().getBackCoverArtUrl()).isEqualTo("http://fakeurl-1");
+ assertThat(selectAlbumQueryResult.getSongs()).hasSize(3);
+ assertThat(selectAlbumQueryResult.getSongs()).usingFieldByFieldElementComparator().containsExactlyInAnyOrder(aHappySong, aSadSong, anotherHappySong);
+
+ selectAlbumQueryResult = albumRepository.findById(newcomerAlbum.getId()).get();
+ assertThat(selectAlbumQueryResult.getName()).isEqualTo("The Newcomer Album");
+ assertThat(selectAlbumQueryResult.getCoverArt().getFrontCoverArtUrl()).isEqualTo("http://fakeurl-2");
+ assertThat(selectAlbumQueryResult.getCoverArt().getBackCoverArtUrl()).isEqualTo("http://fakeurl-3");
+ assertThat(selectAlbumQueryResult.getSongs()).hasSize(1);
+ assertThat(selectAlbumQueryResult.getSongs()).usingFieldByFieldElementComparator().containsExactlyInAnyOrder(aNewSong);
+
+ Song selectSongQueryResult;
+
+ selectSongQueryResult = songRepository.findById(emptySong.getId()).get();
+ assertThat(selectSongQueryResult.getName()).isNull();
+ assertThat(selectSongQueryResult.getLength()).isZero();
+ assertThat(selectSongQueryResult.getArtist()).isNull();
+
+ selectSongQueryResult = songRepository.findById(aHappySong.getId()).get();
+ assertThat(selectSongQueryResult.getName()).isEqualTo("A Happy Song");
+ assertThat(selectSongQueryResult.getLength()).isEqualTo(Duration.ofMinutes(4).getSeconds());
+ assertThat(selectSongQueryResult.getArtist().getName()).isEqualTo("Superstar");
+ assertThat(selectSongQueryResult.getArtist().getGenre()).isEqualTo("Pop");
+ assertThat(selectSongQueryResult.getArtist().getCountry()).isEqualTo("England");
+
+ selectSongQueryResult = songRepository.findById(aSadSong.getId()).get();
+ assertThat(selectSongQueryResult.getName()).isEqualTo("A Sad Song");
+ assertThat(selectSongQueryResult.getLength()).isEqualTo(Duration.ofMinutes(2).getSeconds());
+ assertThat(selectSongQueryResult.getArtist().getName()).isEqualTo("Superstar");
+ assertThat(selectSongQueryResult.getArtist().getGenre()).isEqualTo("Pop");
+ assertThat(selectSongQueryResult.getArtist().getCountry()).isEqualTo("England");
+
+ selectSongQueryResult = songRepository.findById(anotherHappySong.getId()).get();
+ assertThat(selectSongQueryResult.getName()).isEqualTo("Another Happy Song");
+ assertThat(selectSongQueryResult.getLength()).isEqualTo(Duration.ofMinutes(3).getSeconds());
+ assertThat(selectSongQueryResult.getArtist().getName()).isEqualTo("Superstar");
+ assertThat(selectSongQueryResult.getArtist().getGenre()).isEqualTo("Pop");
+ assertThat(selectSongQueryResult.getArtist().getCountry()).isEqualTo("England");
+
+ selectSongQueryResult = songRepository.findById(aNewSong.getId()).get();
+ assertThat(selectSongQueryResult.getName()).isEqualTo("A New Song");
+ assertThat(selectSongQueryResult.getLength()).isEqualTo(Duration.ofMinutes(5).getSeconds());
+ assertThat(selectSongQueryResult.getArtist().getName()).isEqualTo("Newcomer");
+ assertThat(selectSongQueryResult.getArtist().getGenre()).isEqualTo("Reggae");
+ assertThat(selectSongQueryResult.getArtist().getCountry()).isEqualTo("Jamaica");
+ }
+
+ @Test
+ void whenSavingAHibernateTypeYearMonth_thenTheCorrectValueIsStoredInTheDatabase() {
+ Song mySong = new Song();
+ YearMonth now = YearMonth.of(2019, 12);
+ mySong.setArtist(new Artist());
+ mySong.setName("My Song");
+ mySong.setLength(Duration.ofMinutes(1).getSeconds());
+ mySong.setRecordedOn(now);
+ mySong = songRepository.save(mySong);
+
+ Song selectSongQueryResult;
+ selectSongQueryResult = songRepository.findById(mySong.getId()).get();
+ assertThat(selectSongQueryResult.getRecordedOn().getYear()).isEqualTo(2019);
+ assertThat(selectSongQueryResult.getRecordedOn().getMonthValue()).isEqualTo(12);
+ }
+}
diff --git a/persistence-modules/pom.xml b/persistence-modules/pom.xml
index 78da896861..a03ba1ec5d 100644
--- a/persistence-modules/pom.xml
+++ b/persistence-modules/pom.xml
@@ -24,6 +24,7 @@
hibernate-mapping
hibernate-ogm
hibernate-annotations
+ hibernate-libraries
hibernate-jpa
hibernate-queries
hibernate-enterprise
diff --git a/persistence-modules/spring-boot-persistence-2/README.md b/persistence-modules/spring-boot-persistence-2/README.md
index e0479ffe10..392218d2bf 100644
--- a/persistence-modules/spring-boot-persistence-2/README.md
+++ b/persistence-modules/spring-boot-persistence-2/README.md
@@ -3,6 +3,6 @@
- [Using JDBI with Spring Boot](https://www.baeldung.com/spring-boot-jdbi)
- [Configuring a Tomcat Connection Pool in Spring Boot](https://www.baeldung.com/spring-boot-tomcat-connection-pool)
- [Integrating Spring Boot with HSQLDB](https://www.baeldung.com/spring-boot-hsqldb)
-- [List of In-Memory Databases](http://www.baeldung.com/java-in-memory-databases)
+- [List of In-Memory Databases](https://www.baeldung.com/java-in-memory-databases)
- [Oracle Connection Pooling With Spring](https://www.baeldung.com/spring-oracle-connection-pooling)
- More articles: [[<-- prev]](../spring-boot-persistence)
diff --git a/persistence-modules/spring-boot-persistence-2/pom.xml b/persistence-modules/spring-boot-persistence-2/pom.xml
index 33b33d7160..f36d8fc43f 100644
--- a/persistence-modules/spring-boot-persistence-2/pom.xml
+++ b/persistence-modules/spring-boot-persistence-2/pom.xml
@@ -120,6 +120,22 @@
org.hsqldb
hsqldb
+
+
+ com.oracle.database.jdbc
+ ojdbc8
+ ${oracle-database.version}
+
+
+ com.oracle.database.ha
+ ons
+ ${oracle-database.version}
+
+
+ com.oracle.database.jdbc
+ ucp
+ ${oracle-database.version}
+
@@ -128,23 +144,6 @@
org.springframework.boot
spring-boot-maven-plugin
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
- com/baeldung/spring/oracle/pooling/configuration/OracleConfiguration.java
- com/baeldung/spring/oracle/pooling/configuration/OracleUCPConfiguration.java
-
-
- com/baeldung/spring/oracle/pooling/SpringOraclePoolingApplicationOracleLiveTest.java
- com/baeldung/spring/oracle/pooling/SpringOraclePoolingApplicationOracleUCPLiveTest.java
-
-
-
@@ -152,6 +151,7 @@
3.9.1
2.1.8.RELEASE
0.9.5.2
+ 19.6.0.0
diff --git a/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/tomcatconnectionpool/test/application/SpringBootTomcatConnectionPoolIntegrationTest.java b/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/tomcatconnectionpool/test/application/SpringBootTomcatConnectionPoolIntegrationTest.java
index 5400d76fbe..4422c27150 100644
--- a/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/tomcatconnectionpool/test/application/SpringBootTomcatConnectionPoolIntegrationTest.java
+++ b/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/tomcatconnectionpool/test/application/SpringBootTomcatConnectionPoolIntegrationTest.java
@@ -4,6 +4,7 @@ import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import com.baeldung.tomcatconnectionpool.application.SpringBootConsoleApplication;
@@ -13,6 +14,7 @@ import org.springframework.boot.test.context.SpringBootTest;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {SpringBootConsoleApplication.class})
+@TestPropertySource(properties = "spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource")
public class SpringBootTomcatConnectionPoolIntegrationTest {
@Autowired
diff --git a/persistence-modules/spring-boot-persistence-h2/pom.xml b/persistence-modules/spring-boot-persistence-h2/pom.xml
index 777bc6cb2d..7070b5e674 100644
--- a/persistence-modules/spring-boot-persistence-h2/pom.xml
+++ b/persistence-modules/spring-boot-persistence-h2/pom.xml
@@ -35,23 +35,21 @@
${lombok.version}
compile
-
- org.hibernate
- hibernate-core
- ${hibernate.version}
-
com.vladmihalcea
db-util
${db-util.version}
+
+ net.bytebuddy
+ byte-buddy
+ ${byte-buddy.version}
+
com.baeldung.h2db.demo.server.SpringBootApp
- 2.0.4.RELEASE
- 5.3.11.Final
1.0.4
diff --git a/persistence-modules/spring-boot-persistence-h2/src/main/resources/application.properties b/persistence-modules/spring-boot-persistence-h2/src/main/resources/application.properties
index ed1ffc63c3..0466eaac79 100644
--- a/persistence-modules/spring-boot-persistence-h2/src/main/resources/application.properties
+++ b/persistence-modules/spring-boot-persistence-h2/src/main/resources/application.properties
@@ -8,5 +8,4 @@ spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.validator.apply_to_ddl=false
#spring.jpa.properties.hibernate.check_nullability=true
spring.h2.console.enabled=true
-spring.h2.console.path=/h2-console
-debug=true
\ No newline at end of file
+spring.h2.console.path=/h2-console
\ No newline at end of file
diff --git a/persistence-modules/spring-boot-persistence/README.MD b/persistence-modules/spring-boot-persistence/README.MD
index d6ef239448..5b9fbf7b79 100644
--- a/persistence-modules/spring-boot-persistence/README.MD
+++ b/persistence-modules/spring-boot-persistence/README.MD
@@ -1,8 +1,8 @@
### Relevant Articles:
-- [Spring Boot with Multiple SQL Import Files](http://www.baeldung.com/spring-boot-sql-import-files)
-- [Configuring Separate Spring DataSource for Tests](http://www.baeldung.com/spring-testing-separate-data-source)
-- [Quick Guide on Loading Initial Data with Spring Boot](http://www.baeldung.com/spring-boot-data-sql-and-schema-sql)
+- [Spring Boot with Multiple SQL Import Files](https://www.baeldung.com/spring-boot-sql-import-files)
+- [Configuring Separate Spring DataSource for Tests](https://www.baeldung.com/spring-testing-separate-data-source)
+- [Quick Guide on Loading Initial Data with Spring Boot](https://www.baeldung.com/spring-boot-data-sql-and-schema-sql)
- [Configuring a DataSource Programmatically in Spring Boot](https://www.baeldung.com/spring-boot-configure-data-source-programmatic)
- [Resolving “Failed to Configure a DataSource” Error](https://www.baeldung.com/spring-boot-failed-to-configure-data-source)
- [Hibernate Field Naming with Spring Boot](https://www.baeldung.com/hibernate-field-naming-spring-boot)
diff --git a/persistence-modules/spring-jpa/README.md b/persistence-modules/spring-jpa/README.md
index 599a667a13..5db88cd5ee 100644
--- a/persistence-modules/spring-jpa/README.md
+++ b/persistence-modules/spring-jpa/README.md
@@ -13,7 +13,6 @@
- [Transactions with Spring and JPA](https://www.baeldung.com/transaction-configuration-with-jpa-and-spring)
- [Use Criteria Queries in a Spring Data Application](https://www.baeldung.com/spring-data-criteria-queries)
- [Many-To-Many Relationship in JPA](https://www.baeldung.com/jpa-many-to-many)
-- [Spring Persistence (Hibernate and JPA) with a JNDI datasource](https://www.baeldung.com/spring-persistence-hibernate-and-jpa-with-a-jndi-datasource/)
### Eclipse Config
diff --git a/rabbitmq/src/main/java/com/baeldung/consumer/Receiver.java b/rabbitmq/src/main/java/com/baeldung/consumer/Receiver.java
index d0612406e9..b2779a6b29 100644
--- a/rabbitmq/src/main/java/com/baeldung/consumer/Receiver.java
+++ b/rabbitmq/src/main/java/com/baeldung/consumer/Receiver.java
@@ -17,7 +17,7 @@ public class Receiver {
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
- Consumer consumer = new DefaultConsumer(channel) {
+ DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope, AMQP.BasicProperties properties,
diff --git a/spring-boot-modules/spring-boot-keycloak/pom.xml b/spring-boot-modules/spring-boot-keycloak/pom.xml
index c29c1a738b..8e917df2b7 100644
--- a/spring-boot-modules/spring-boot-keycloak/pom.xml
+++ b/spring-boot-modules/spring-boot-keycloak/pom.xml
@@ -11,9 +11,9 @@
com.baeldung
- parent-boot-1
+ parent-boot-2
0.0.1-SNAPSHOT
- ../../parent-boot-1
+ ../../parent-boot-2
@@ -41,6 +41,10 @@
org.springframework.boot
spring-boot-starter-data-jpa
+
+ net.bytebuddy
+ byte-buddy
+
org.springframework.boot
@@ -76,7 +80,7 @@
- 3.3.0.Final
+ 10.0.1
diff --git a/spring-boot-modules/spring-boot-libraries/pom.xml b/spring-boot-modules/spring-boot-libraries/pom.xml
index 090967d8a8..189eb4cf1a 100644
--- a/spring-boot-modules/spring-boot-libraries/pom.xml
+++ b/spring-boot-modules/spring-boot-libraries/pom.xml
@@ -87,7 +87,35 @@
javase
${zxing.version}
-
+
+
+ com.github.vladimir-bukhtoyarov
+ bucket4j-core
+ ${bucket4j.version}
+
+
+ com.giffing.bucket4j.spring.boot.starter
+ bucket4j-spring-boot-starter
+ ${bucket4j-spring-boot-starter.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+ javax.cache
+ cache-api
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ ${caffeine.version}
+
+
+ com.github.ben-manes.caffeine
+ jcache
+ ${caffeine.version}
+
@@ -200,6 +228,9 @@
2.1
2.6.0
3.3.0
+ 4.10.0
+ 0.2.0
+ 2.8.2
diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bootstarterapp/Bucket4jRateLimitApp.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bootstarterapp/Bucket4jRateLimitApp.java
new file mode 100644
index 0000000000..f16d347f85
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bootstarterapp/Bucket4jRateLimitApp.java
@@ -0,0 +1,21 @@
+package com.baeldung.ratelimiting.bootstarterapp;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.cache.annotation.EnableCaching;
+
+@SpringBootApplication(scanBasePackages = "com.baeldung.ratelimiting", exclude = {
+ DataSourceAutoConfiguration.class,
+ SecurityAutoConfiguration.class,
+})
+@EnableCaching
+public class Bucket4jRateLimitApp {
+
+ public static void main(String[] args) {
+ new SpringApplicationBuilder(Bucket4jRateLimitApp.class)
+ .properties("spring.config.location=classpath:ratelimiting/application-bucket4j-starter.yml")
+ .run(args);
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jRateLimitApp.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jRateLimitApp.java
new file mode 100644
index 0000000000..bb179b9b38
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jRateLimitApp.java
@@ -0,0 +1,35 @@
+package com.baeldung.ratelimiting.bucket4japp;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import com.baeldung.ratelimiting.bucket4japp.interceptor.RateLimitInterceptor;
+
+@SpringBootApplication(scanBasePackages = "com.baeldung.ratelimiting", exclude = {
+ DataSourceAutoConfiguration.class,
+ SecurityAutoConfiguration.class
+})
+public class Bucket4jRateLimitApp implements WebMvcConfigurer {
+
+ @Autowired
+ @Lazy
+ private RateLimitInterceptor interceptor;
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(interceptor)
+ .addPathPatterns("/api/v1/area/**");
+ }
+
+ public static void main(String[] args) {
+ new SpringApplicationBuilder(Bucket4jRateLimitApp.class)
+ .properties("spring.config.location=classpath:ratelimiting/application-bucket4j.yml")
+ .run(args);
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/interceptor/RateLimitInterceptor.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/interceptor/RateLimitInterceptor.java
new file mode 100644
index 0000000000..8a18d6c2b5
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/interceptor/RateLimitInterceptor.java
@@ -0,0 +1,57 @@
+package com.baeldung.ratelimiting.bucket4japp.interceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import com.baeldung.ratelimiting.bucket4japp.service.PricingPlanService;
+
+import io.github.bucket4j.Bucket;
+import io.github.bucket4j.ConsumptionProbe;
+
+@Component
+public class RateLimitInterceptor implements HandlerInterceptor {
+
+ private static final String HEADER_API_KEY = "X-api-key";
+ private static final String HEADER_LIMIT_REMAINING = "X-Rate-Limit-Remaining";
+ private static final String HEADER_RETRY_AFTER = "X-Rate-Limit-Retry-After-Seconds";
+
+ @Autowired
+ private PricingPlanService pricingPlanService;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+
+ String apiKey = request.getHeader(HEADER_API_KEY);
+
+ if (apiKey == null || apiKey.isEmpty()) {
+ response.sendError(HttpStatus.BAD_REQUEST.value(), "Missing Header: " + HEADER_API_KEY);
+ return false;
+ }
+
+ Bucket tokenBucket = pricingPlanService.resolveBucket(apiKey);
+
+ ConsumptionProbe probe = tokenBucket.tryConsumeAndReturnRemaining(1);
+
+ if (probe.isConsumed()) {
+
+ response.addHeader(HEADER_LIMIT_REMAINING, String.valueOf(probe.getRemainingTokens()));
+ return true;
+
+ } else {
+
+ long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000;
+
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.addHeader(HEADER_RETRY_AFTER, String.valueOf(waitForRefill));
+ response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(), "You have exhausted your API Request Quota"); // 429
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/service/PricingPlan.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/service/PricingPlan.java
new file mode 100644
index 0000000000..27c30ba3a0
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/service/PricingPlan.java
@@ -0,0 +1,42 @@
+package com.baeldung.ratelimiting.bucket4japp.service;
+
+import java.time.Duration;
+
+import io.github.bucket4j.Bandwidth;
+import io.github.bucket4j.Refill;
+
+public enum PricingPlan {
+
+ FREE(20),
+
+ BASIC(40),
+
+ PROFESSIONAL(100);
+
+ private int bucketCapacity;
+
+ private PricingPlan(int bucketCapacity) {
+ this.bucketCapacity = bucketCapacity;
+ }
+
+ Bandwidth getLimit() {
+ return Bandwidth.classic(bucketCapacity, Refill.intervally(bucketCapacity, Duration.ofHours(1)));
+ }
+
+ public int bucketCapacity() {
+ return bucketCapacity;
+ }
+
+ static PricingPlan resolvePlanFromApiKey(String apiKey) {
+ if (apiKey == null || apiKey.isEmpty()) {
+ return FREE;
+
+ } else if (apiKey.startsWith("PX001-")) {
+ return PROFESSIONAL;
+
+ } else if (apiKey.startsWith("BX001-")) {
+ return BASIC;
+ }
+ return FREE;
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/service/PricingPlanService.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/service/PricingPlanService.java
new file mode 100644
index 0000000000..7d8a718601
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/service/PricingPlanService.java
@@ -0,0 +1,31 @@
+package com.baeldung.ratelimiting.bucket4japp.service;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.springframework.stereotype.Service;
+
+import io.github.bucket4j.Bandwidth;
+import io.github.bucket4j.Bucket;
+import io.github.bucket4j.Bucket4j;
+
+@Service
+public class PricingPlanService {
+
+ private final Map cache = new ConcurrentHashMap<>();
+
+ public Bucket resolveBucket(String apiKey) {
+ return cache.computeIfAbsent(apiKey, this::newBucket);
+ }
+
+ private Bucket newBucket(String apiKey) {
+ PricingPlan pricingPlan = PricingPlan.resolvePlanFromApiKey(apiKey);
+ return bucket(pricingPlan.getLimit());
+ }
+
+ private Bucket bucket(Bandwidth limit) {
+ return Bucket4j.builder()
+ .addLimit(limit)
+ .build();
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/controller/AreaCalculationController.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/controller/AreaCalculationController.java
new file mode 100644
index 0000000000..f3fb63ebdd
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/controller/AreaCalculationController.java
@@ -0,0 +1,29 @@
+package com.baeldung.ratelimiting.controller;
+
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+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;
+
+import com.baeldung.ratelimiting.dto.AreaV1;
+import com.baeldung.ratelimiting.dto.RectangleDimensionsV1;
+import com.baeldung.ratelimiting.dto.TriangleDimensionsV1;
+
+@RestController
+@RequestMapping(value = "/api/v1/area", consumes = MediaType.APPLICATION_JSON_VALUE)
+class AreaCalculationController {
+
+ @PostMapping(value = "/rectangle", produces = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity rectangle(@RequestBody RectangleDimensionsV1 dimensions) {
+
+ return ResponseEntity.ok(new AreaV1("rectangle", dimensions.getLength() * dimensions.getWidth()));
+ }
+
+ @PostMapping(value = "/triangle", produces = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity triangle(@RequestBody TriangleDimensionsV1 dimensions) {
+
+ return ResponseEntity.ok(new AreaV1("triangle", 0.5d * dimensions.getHeight() * dimensions.getBase()));
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/AreaV1.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/AreaV1.java
new file mode 100644
index 0000000000..78097f55b2
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/AreaV1.java
@@ -0,0 +1,20 @@
+package com.baeldung.ratelimiting.dto;
+
+public class AreaV1 {
+
+ private String shape;
+ private Double area;
+
+ public AreaV1(String shape, Double area) {
+ this.area = area;
+ this.shape = shape;
+ }
+
+ public Double getArea() {
+ return area;
+ }
+
+ public String getShape() {
+ return shape;
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/RectangleDimensionsV1.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/RectangleDimensionsV1.java
new file mode 100644
index 0000000000..e3c17e1ba7
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/RectangleDimensionsV1.java
@@ -0,0 +1,15 @@
+package com.baeldung.ratelimiting.dto;
+
+public class RectangleDimensionsV1 {
+
+ private double length;
+ private double width;
+
+ public double getLength() {
+ return length;
+ }
+
+ public double getWidth() {
+ return width;
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/TriangleDimensionsV1.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/TriangleDimensionsV1.java
new file mode 100644
index 0000000000..44c954bded
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/TriangleDimensionsV1.java
@@ -0,0 +1,15 @@
+package com.baeldung.ratelimiting.dto;
+
+public class TriangleDimensionsV1 {
+
+ private double base;
+ private double height;
+
+ public double getBase() {
+ return base;
+ }
+
+ public double getHeight() {
+ return height;
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries/src/main/resources/ratelimiting/application-bucket4j-starter.yml b/spring-boot-modules/spring-boot-libraries/src/main/resources/ratelimiting/application-bucket4j-starter.yml
new file mode 100644
index 0000000000..ecc9f22e0a
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/main/resources/ratelimiting/application-bucket4j-starter.yml
@@ -0,0 +1,40 @@
+server:
+ port: 9001
+
+spring:
+ application:
+ name: bucket4j-starter-api-rate-limit-app
+ mvc:
+ throw-exception-if-no-handler-found: true
+ resources:
+ add-mappings: false
+ cache:
+ cache-names:
+ - rate-limit-buckets
+ caffeine:
+ spec: maximumSize=100000,expireAfterAccess=3600s
+
+bucket4j:
+ enabled: true
+ filters:
+ - cache-name: rate-limit-buckets
+ url: /api/v1/area.*
+ http-response-body: "{ \"status\": 429, \"error\": \"Too Many Requests\", \"message\": \"You have exhausted your API Request Quota\" }"
+ rate-limits:
+ - expression: "getHeader('X-api-key')"
+ execute-condition: "getHeader('X-api-key').startsWith('PX001-')"
+ bandwidths:
+ - capacity: 100
+ time: 1
+ unit: hours
+ - expression: "getHeader('X-api-key')"
+ execute-condition: "getHeader('X-api-key').startsWith('BX001-')"
+ bandwidths:
+ - capacity: 40
+ time: 1
+ unit: hours
+ - expression: "getHeader('X-api-key')"
+ bandwidths:
+ - capacity: 20
+ time: 1
+ unit: hours
diff --git a/spring-boot-modules/spring-boot-libraries/src/main/resources/ratelimiting/application-bucket4j.yml b/spring-boot-modules/spring-boot-libraries/src/main/resources/ratelimiting/application-bucket4j.yml
new file mode 100644
index 0000000000..ae19622d9b
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/main/resources/ratelimiting/application-bucket4j.yml
@@ -0,0 +1,10 @@
+server:
+ port: 9000
+
+spring:
+ application:
+ name: bucket4j-api-rate-limit-app
+ mvc:
+ throw-exception-if-no-handler-found: true
+ resources:
+ add-mappings: false
diff --git a/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bootstarterapp/Bucket4jBootStarterRateLimitIntegrationTest.java b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bootstarterapp/Bucket4jBootStarterRateLimitIntegrationTest.java
new file mode 100644
index 0000000000..d93e61988b
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bootstarterapp/Bucket4jBootStarterRateLimitIntegrationTest.java
@@ -0,0 +1,63 @@
+package com.baeldung.ratelimiting.bootstarterapp;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.RequestBuilder;
+
+import com.baeldung.ratelimiting.bucket4japp.service.PricingPlan;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Bucket4jRateLimitApp.class)
+@TestPropertySource(properties = "spring.config.location=classpath:ratelimiting/application-bucket4j-starter.yml")
+@AutoConfigureMockMvc
+public class Bucket4jBootStarterRateLimitIntegrationTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Test
+ public void givenTriangleAreaCalculator_whenRequestsWithinRateLimit_thenAccepted() throws Exception {
+
+ RequestBuilder request = post("/api/v1/area/triangle").contentType(MediaType.APPLICATION_JSON_VALUE)
+ .content("{ \"height\": 8, \"base\": 10 }")
+ .header("X-api-key", "FX001-UBSZ5YRYQ");
+
+ for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) {
+ mockMvc.perform(request)
+ .andExpect(status().isOk())
+ .andExpect(header().exists("X-Rate-Limit-Remaining"))
+ .andExpect(jsonPath("$.shape", equalTo("triangle")))
+ .andExpect(jsonPath("$.area", equalTo(40d)));
+ }
+ }
+
+ @Test
+ public void givenTriangleAreaCalculator_whenRequestRateLimitTriggered_thenRejected() throws Exception {
+
+ RequestBuilder request = post("/api/v1/area/triangle").contentType(MediaType.APPLICATION_JSON_VALUE)
+ .content("{ \"height\": 8, \"base\": 10 }")
+ .header("X-api-key", "FX001-ZBSY6YSLP");
+
+ for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) {
+ mockMvc.perform(request); // exhaust limit
+ }
+
+ mockMvc.perform(request)
+ .andExpect(status().isTooManyRequests())
+ .andExpect(jsonPath("$.message", equalTo("You have exhausted your API Request Quota")))
+ .andExpect(header().exists("X-Rate-Limit-Retry-After-Seconds"));
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jRateLimitIntegrationTest.java b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jRateLimitIntegrationTest.java
new file mode 100644
index 0000000000..20f57a7021
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jRateLimitIntegrationTest.java
@@ -0,0 +1,61 @@
+package com.baeldung.ratelimiting.bucket4japp;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.RequestBuilder;
+
+import com.baeldung.ratelimiting.bucket4japp.service.PricingPlan;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Bucket4jRateLimitApp.class)
+@AutoConfigureMockMvc
+public class Bucket4jRateLimitIntegrationTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Test
+ public void givenRectangleAreaCalculator_whenRequestsWithinRateLimit_thenAccepted() throws Exception {
+
+ RequestBuilder request = post("/api/v1/area/rectangle").contentType(MediaType.APPLICATION_JSON_VALUE)
+ .content("{ \"length\": 12, \"width\": 10 }")
+ .header("X-api-key", "FX001-UBSZ5YRYQ");
+
+ for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) {
+ mockMvc.perform(request)
+ .andExpect(status().isOk())
+ .andExpect(header().exists("X-Rate-Limit-Remaining"))
+ .andExpect(jsonPath("$.shape", equalTo("rectangle")))
+ .andExpect(jsonPath("$.area", equalTo(120d)));
+ }
+ }
+
+ @Test
+ public void givenReactangleAreaCalculator_whenRequestRateLimitTriggered_thenRejected() throws Exception {
+
+ RequestBuilder request = post("/api/v1/area/rectangle").contentType(MediaType.APPLICATION_JSON_VALUE)
+ .content("{ \"length\": 12, \"width\": 10 }")
+ .header("X-api-key", "FX001-ZBSY6YSLP");
+
+ for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) {
+ mockMvc.perform(request); // exhaust limit
+ }
+
+ mockMvc.perform(request)
+ .andExpect(status().isTooManyRequests())
+ .andExpect(status().reason("You have exhausted your API Request Quota"))
+ .andExpect(header().exists("X-Rate-Limit-Retry-After-Seconds"));
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jUsageUnitTest.java b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jUsageUnitTest.java
new file mode 100644
index 0000000000..fbf63ba403
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jUsageUnitTest.java
@@ -0,0 +1,82 @@
+package com.baeldung.ratelimiting.bucket4japp;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.time.Duration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.Test;
+
+import io.github.bucket4j.Bandwidth;
+import io.github.bucket4j.Bucket;
+import io.github.bucket4j.Bucket4j;
+import io.github.bucket4j.Refill;
+
+public class Bucket4jUsageUnitTest {
+
+ @Test
+ public void givenBucketLimit_whenExceedLimit_thenConsumeReturnsFalse() {
+ Refill refill = Refill.intervally(10, Duration.ofMinutes(1));
+ Bandwidth limit = Bandwidth.classic(10, refill);
+ Bucket bucket = Bucket4j.builder()
+ .addLimit(limit)
+ .build();
+
+ for (int i = 1; i <= 10; i++) {
+ assertTrue(bucket.tryConsume(1));
+ }
+ assertFalse(bucket.tryConsume(1));
+ }
+
+ @Test
+ public void givenMultipletLimits_whenExceedSmallerLimit_thenConsumeReturnsFalse() {
+ Bucket bucket = Bucket4j.builder()
+ .addLimit(Bandwidth.classic(10, Refill.intervally(10, Duration.ofMinutes(1))))
+ .addLimit(Bandwidth.classic(5, Refill.intervally(5, Duration.ofSeconds(20))))
+ .build();
+
+ for (int i = 1; i <= 5; i++) {
+ assertTrue(bucket.tryConsume(1));
+ }
+ assertFalse(bucket.tryConsume(1));
+ }
+
+ @Test
+ public void givenBucketLimit_whenThrottleRequests_thenConsumeReturnsTrue() throws InterruptedException {
+ Refill refill = Refill.intervally(1, Duration.ofSeconds(2));
+ Bandwidth limit = Bandwidth.classic(1, refill);
+ Bucket bucket = Bucket4j.builder()
+ .addLimit(limit)
+ .build();
+
+ assertTrue(bucket.tryConsume(1));
+
+ ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
+ CountDownLatch latch = new CountDownLatch(1);
+
+ executor.schedule(new AssertTryConsume(bucket, latch), 2, TimeUnit.SECONDS);
+
+ latch.await();
+ }
+
+ static class AssertTryConsume implements Runnable {
+
+ private Bucket bucket;
+ private CountDownLatch latch;
+
+ AssertTryConsume(Bucket bucket, CountDownLatch latch) {
+ this.bucket = bucket;
+ this.latch = latch;
+ }
+
+ @Override
+ public void run() {
+ assertTrue(bucket.tryConsume(1));
+ latch.countDown();
+ }
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/PricingPlanServiceUnitTest.java b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/PricingPlanServiceUnitTest.java
new file mode 100644
index 0000000000..325b898779
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/PricingPlanServiceUnitTest.java
@@ -0,0 +1,36 @@
+package com.baeldung.ratelimiting.bucket4japp;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import com.baeldung.ratelimiting.bucket4japp.service.PricingPlan;
+import com.baeldung.ratelimiting.bucket4japp.service.PricingPlanService;
+
+import io.github.bucket4j.Bucket;
+
+public class PricingPlanServiceUnitTest {
+
+ private PricingPlanService service = new PricingPlanService();
+
+ @Test
+ public void givenAPIKey_whenFreePlan_thenReturnFreePlanBucket() {
+ Bucket bucket = service.resolveBucket("FX001-UBSZ5YRYQ");
+
+ assertEquals(PricingPlan.FREE.bucketCapacity(), bucket.getAvailableTokens());
+ }
+
+ @Test
+ public void givenAPIKey_whenBasiclan_thenReturnBasicPlanBucket() {
+ Bucket bucket = service.resolveBucket("BX001-MBSZ5YRYP");
+
+ assertEquals(PricingPlan.BASIC.bucketCapacity(), bucket.getAvailableTokens());
+ }
+
+ @Test
+ public void givenAPIKey_whenProfessionalPlan_thenReturnProfessionalPlanBucket() {
+ Bucket bucket = service.resolveBucket("PX001-NBSZ5YRYY");
+
+ assertEquals(PricingPlan.PROFESSIONAL.bucketCapacity(), bucket.getAvailableTokens());
+ }
+}
diff --git a/spring-boot-modules/spring-boot-mvc-birt/pom.xml b/spring-boot-modules/spring-boot-mvc-birt/pom.xml
index f65b851f30..0e8e231a84 100644
--- a/spring-boot-modules/spring-boot-mvc-birt/pom.xml
+++ b/spring-boot-modules/spring-boot-mvc-birt/pom.xml
@@ -75,7 +75,6 @@
- 2.1.1.RELEASE
com.baeldung.birt.engine.ReportEngineApplication
1.8
1.8
diff --git a/spring-boot-modules/spring-boot-properties/.dockerignore b/spring-boot-modules/spring-boot-properties/.dockerignore
new file mode 100644
index 0000000000..df36044e46
--- /dev/null
+++ b/spring-boot-modules/spring-boot-properties/.dockerignore
@@ -0,0 +1,13 @@
+# Logs
+logs
+*.log
+
+# Git
+.git
+.cache
+
+# Classes
+**/*.class
+
+# Ignore md files
+*.md
diff --git a/spring-boot-modules/spring-boot-properties/Dockerfile b/spring-boot-modules/spring-boot-properties/Dockerfile
new file mode 100644
index 0000000000..d6bd2a95ae
--- /dev/null
+++ b/spring-boot-modules/spring-boot-properties/Dockerfile
@@ -0,0 +1,10 @@
+FROM maven:3.6.0-jdk-11
+WORKDIR /code/spring-boot-modules/spring-boot-properties/
+COPY ./spring-boot-modules/spring-boot-properties/pom.xml .
+COPY ./spring-boot-modules/spring-boot-properties/src ./src
+COPY ./parent-boot-2/pom.xml /code/parent-boot-2/pom.xml
+COPY ./pom.xml /code/pom.xml
+COPY ./custom-pmd-0.0.1.jar /code/custom-pmd-0.0.1.jar
+COPY ./baeldung-pmd-rules.xml /code/baeldung-pmd-rules.xml
+RUN mvn dependency:resolve
+CMD ["mvn", "spring-boot:run"]
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-properties/pom.xml b/spring-boot-modules/spring-boot-properties/pom.xml
index ef9c084f4c..98d328bd19 100644
--- a/spring-boot-modules/spring-boot-properties/pom.xml
+++ b/spring-boot-modules/spring-boot-properties/pom.xml
@@ -128,7 +128,8 @@
4.4.11
@
2.2.4.RELEASE
- com.baeldung.buildproperties.Application
+
+ com.baeldung.yaml.MyApplication
diff --git a/spring-boot-modules/spring-boot-properties/src/main/resources/application.yml b/spring-boot-modules/spring-boot-properties/src/main/resources/application.yml
index 6fc6f67cd0..4914ff15f7 100644
--- a/spring-boot-modules/spring-boot-properties/src/main/resources/application.yml
+++ b/spring-boot-modules/spring-boot-properties/src/main/resources/application.yml
@@ -1,7 +1,14 @@
+spring:
+ profiles:
+ active:
+ - test
+
+---
+
spring:
profiles: test
name: test-YAML
-environment: test
+environment: testing
servers:
- www.abc.test.com
- www.xyz.test.com
@@ -15,3 +22,13 @@ environment: production
servers:
- www.abc.com
- www.xyz.com
+
+---
+
+spring:
+ profiles: dev
+name: ${DEV_NAME:dev-YAML}
+environment: development
+servers:
+ - www.abc.dev.com
+ - www.xyz.dev.com
diff --git a/spring-boot-modules/spring-boot-properties/src/test/java/com/baeldung/yaml/YAMLDevIntegrationTest.java b/spring-boot-modules/spring-boot-properties/src/test/java/com/baeldung/yaml/YAMLDevIntegrationTest.java
new file mode 100644
index 0000000000..8dfc4c2208
--- /dev/null
+++ b/spring-boot-modules/spring-boot-properties/src/test/java/com/baeldung/yaml/YAMLDevIntegrationTest.java
@@ -0,0 +1,25 @@
+package com.baeldung.yaml;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = MyApplication.class)
+@TestPropertySource(properties = {"spring.profiles.active = dev"})
+class YAMLDevIntegrationTest {
+
+ @Autowired
+ private YAMLConfig config;
+
+ @Test
+ void whenProfileTest_thenNameTesting() {
+ assertTrue("development".equalsIgnoreCase(config.getEnvironment()));
+ assertTrue("dev-YAML".equalsIgnoreCase(config.getName()));
+ }
+}
diff --git a/spring-boot-modules/spring-boot-properties/src/test/java/com/baeldung/yaml/YAMLIntegrationTest.java b/spring-boot-modules/spring-boot-properties/src/test/java/com/baeldung/yaml/YAMLIntegrationTest.java
new file mode 100644
index 0000000000..090d5c592e
--- /dev/null
+++ b/spring-boot-modules/spring-boot-properties/src/test/java/com/baeldung/yaml/YAMLIntegrationTest.java
@@ -0,0 +1,24 @@
+package com.baeldung.yaml;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = MyApplication.class)
+class YAMLIntegrationTest {
+
+ @Autowired
+ private YAMLConfig config;
+
+ @Test
+ void whenProfileTest_thenNameTesting() {
+ assertTrue("testing".equalsIgnoreCase(config.getEnvironment()));
+ assertTrue("test-YAML".equalsIgnoreCase(config.getName()));
+ }
+}
diff --git a/spring-boot-modules/spring-boot-springdoc/pom.xml b/spring-boot-modules/spring-boot-springdoc/pom.xml
index 1c1f27b5a5..4bede8c796 100644
--- a/spring-boot-modules/spring-boot-springdoc/pom.xml
+++ b/spring-boot-modules/spring-boot-springdoc/pom.xml
@@ -66,6 +66,22 @@
spring-restdocs-restassured
test
+
+
+
+ org.springdoc
+ springdoc-openapi-kotlin
+ ${springdoc.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jre8
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
@@ -97,6 +113,41 @@
+
+
+ kotlin-maven-plugin
+ org.jetbrains.kotlin
+ ${kotlin.version}
+
+
+ spring
+
+ ${java.version}
+
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
@@ -116,6 +167,7 @@
5.2.10.Final
1.2.32
1.5.6
+ 1.2.71
${project.build.directory}/generated-snippets
diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/controller/BookController.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/controller/BookController.java
index 05f8c5a946..326a97149b 100644
--- a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/controller/BookController.java
+++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/controller/BookController.java
@@ -25,6 +25,13 @@ import com.baeldung.springdoc.exception.BookNotFoundException;
import com.baeldung.springdoc.model.Book;
import com.baeldung.springdoc.repository.BookRepository;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+
@RestController
@RequestMapping("/api/book")
public class BookController {
@@ -32,8 +39,15 @@ public class BookController {
@Autowired
private BookRepository repository;
+ // @formatter:off
+ @Operation(summary = "Get a book by its id")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Found the book",
+ content = { @Content(mediaType = "application/json", schema = @Schema(implementation = Book.class)) }),
+ @ApiResponse(responseCode = "400", description = "Invalid id supplied", content = @Content),
+ @ApiResponse(responseCode = "404", description = "Book not found", content = @Content) }) // @formatter:on
@GetMapping("/{id}")
- public Book findById(@PathVariable long id) {
+ public Book findById(@Parameter(description = "id of book to be searched") @PathVariable long id) {
return repository.findById(id)
.orElseThrow(() -> new BookNotFoundException());
}
diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/kotlin/Foo.kt b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/kotlin/Foo.kt
new file mode 100644
index 0000000000..3bc3c8fe61
--- /dev/null
+++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/kotlin/Foo.kt
@@ -0,0 +1,17 @@
+package com.baeldung.springdoc.kotlin
+
+import javax.persistence.Entity
+import javax.persistence.GeneratedValue
+import javax.persistence.Id
+import javax.validation.constraints.NotBlank
+import javax.validation.constraints.Size
+
+@Entity
+data class Foo(
+ @Id
+ val id: Long = 0,
+
+ @NotBlank
+ @Size(min = 0, max = 50)
+ val name: String = ""
+)
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/kotlin/FooController.kt b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/kotlin/FooController.kt
new file mode 100644
index 0000000000..d3ecd6a6ba
--- /dev/null
+++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/kotlin/FooController.kt
@@ -0,0 +1,30 @@
+package com.baeldung.springdoc.kotlin
+
+import io.swagger.v3.oas.annotations.Operation
+import io.swagger.v3.oas.annotations.media.ArraySchema
+import io.swagger.v3.oas.annotations.media.Content
+import io.swagger.v3.oas.annotations.media.Schema
+import io.swagger.v3.oas.annotations.responses.ApiResponse
+import io.swagger.v3.oas.annotations.responses.ApiResponses
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+@RequestMapping("/")
+class FooController() {
+ val fooList: List = listOf(Foo(1, "one"), Foo(2, "two"))
+
+ @Operation(summary = "Get all foos")
+ @ApiResponses(value = [
+ ApiResponse(responseCode = "200", description = "Found Foos", content = [
+ (Content(mediaType = "application/json", array = (
+ ArraySchema(schema = Schema(implementation = Foo::class)))))]),
+ ApiResponse(responseCode = "400", description = "Bad request", content = [Content()]),
+ ApiResponse(responseCode = "404", description = "Did not find any Foos", content = [Content()])]
+ )
+ @GetMapping("/foo")
+ fun getAllFoos(): List = fooList
+}
+
+
diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/resources/application.properties b/spring-boot-modules/spring-boot-springdoc/src/main/resources/application.properties
index 7debdc6503..0eecfbb1c4 100644
--- a/spring-boot-modules/spring-boot-springdoc/src/main/resources/application.properties
+++ b/spring-boot-modules/spring-boot-springdoc/src/main/resources/application.properties
@@ -1,5 +1,6 @@
# custom path for swagger-ui
springdoc.swagger-ui.path=/swagger-ui-custom.html
+springdoc.swagger-ui.operationsSorter=method
# custom path for api docs
springdoc.api-docs.path=/api-docs
diff --git a/spring-core-4/pom.xml b/spring-core-4/pom.xml
index 53f7ca6912..fbec5ea9eb 100644
--- a/spring-core-4/pom.xml
+++ b/spring-core-4/pom.xml
@@ -24,6 +24,16 @@
spring-core
${spring.version}
+
+ org.springframework
+ spring-expression
+ ${spring.version}
+
+
+ com.google.guava
+ guava
+ 28.2-jre
+
org.springframework
spring-test
@@ -42,6 +52,18 @@
${junit-jupiter.version}
test
+
+ org.awaitility
+ awaitility
+ 4.0.2
+ test
+
+
+ org.assertj
+ assertj-core
+ 2.9.1
+ test
+
@@ -60,4 +82,4 @@
2.2.2.RELEASE
-
\ No newline at end of file
+
diff --git a/spring-core-4/src/main/java/com/baeldung/postprocessor/GlobalEventBus.java b/spring-core-4/src/main/java/com/baeldung/postprocessor/GlobalEventBus.java
new file mode 100644
index 0000000000..8b95ea7c6f
--- /dev/null
+++ b/spring-core-4/src/main/java/com/baeldung/postprocessor/GlobalEventBus.java
@@ -0,0 +1,39 @@
+package com.baeldung.postprocessor;
+
+import com.google.common.eventbus.AsyncEventBus;
+import com.google.common.eventbus.EventBus;
+
+import java.util.concurrent.Executors;
+
+@SuppressWarnings("ALL")
+public final class GlobalEventBus {
+
+ public static final String GLOBAL_EVENT_BUS_EXPRESSION = "T(com.baeldung.postprocessor.GlobalEventBus).getEventBus()";
+
+ private static final String IDENTIFIER = "global-event-bus";
+
+ private static final GlobalEventBus GLOBAL_EVENT_BUS = new GlobalEventBus();
+
+ private final EventBus eventBus = new AsyncEventBus(IDENTIFIER, Executors.newCachedThreadPool());
+
+ private GlobalEventBus() {
+ }
+
+ public static GlobalEventBus getInstance() {
+ return GlobalEventBus.GLOBAL_EVENT_BUS;
+ }
+
+ public static EventBus getEventBus() {
+ return GlobalEventBus.GLOBAL_EVENT_BUS.eventBus;
+ }
+
+ public static void subscribe(Object obj) {
+ getEventBus().register(obj);
+ }
+ public static void unsubscribe(Object obj) {
+ getEventBus().unregister(obj);
+ }
+ public static void post(Object event) {
+ getEventBus().post(event);
+ }
+}
diff --git a/spring-core-4/src/main/java/com/baeldung/postprocessor/GuavaEventBusBeanFactoryPostProcessor.java b/spring-core-4/src/main/java/com/baeldung/postprocessor/GuavaEventBusBeanFactoryPostProcessor.java
new file mode 100644
index 0000000000..fba31fde6a
--- /dev/null
+++ b/spring-core-4/src/main/java/com/baeldung/postprocessor/GuavaEventBusBeanFactoryPostProcessor.java
@@ -0,0 +1,63 @@
+package com.baeldung.postprocessor;
+
+import com.google.common.eventbus.EventBus;
+
+import java.util.Iterator;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.aop.framework.Advised;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.FatalBeanException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionException;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+
+@SuppressWarnings("ALL")
+public class GuavaEventBusBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+ private final SpelExpressionParser expressionParser = new SpelExpressionParser();
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+ for (Iterator names = beanFactory.getBeanNamesIterator(); names.hasNext(); ) {
+ Object proxy = this.getTargetObject(beanFactory.getBean(names.next()));
+ final Subscriber annotation = AnnotationUtils.getAnnotation(proxy.getClass(), Subscriber.class);
+ if (annotation == null)
+ continue;
+ this.logger.info("{}: processing bean of type {} during initialization", this.getClass().getSimpleName(),
+ proxy.getClass().getName());
+ final String annotationValue = annotation.value();
+ try {
+ final Expression expression = this.expressionParser.parseExpression(annotationValue);
+ final Object value = expression.getValue();
+ if (!(value instanceof EventBus)) {
+ this.logger.error("{}: expression {} did not evaluate to an instance of EventBus for bean of type {}",
+ this.getClass().getSimpleName(), annotationValue, proxy.getClass().getSimpleName());
+ return;
+ }
+ final EventBus eventBus = (EventBus)value;
+ eventBus.register(proxy);
+ } catch (ExpressionException ex) {
+ this.logger.error("{}: unable to parse/evaluate expression {} for bean of type {}", this.getClass().getSimpleName(),
+ annotationValue, proxy.getClass().getName());
+ }
+ }
+ }
+
+ private Object getTargetObject(Object proxy) throws BeansException {
+ if (AopUtils.isJdkDynamicProxy(proxy)) {
+ try {
+ return ((Advised)proxy).getTargetSource().getTarget();
+ } catch (Exception e) {
+ throw new FatalBeanException("Error getting target of JDK proxy", e);
+ }
+ }
+ return proxy;
+ }
+}
diff --git a/spring-core-4/src/main/java/com/baeldung/postprocessor/GuavaEventBusBeanPostProcessor.java b/spring-core-4/src/main/java/com/baeldung/postprocessor/GuavaEventBusBeanPostProcessor.java
new file mode 100644
index 0000000000..677c839444
--- /dev/null
+++ b/spring-core-4/src/main/java/com/baeldung/postprocessor/GuavaEventBusBeanPostProcessor.java
@@ -0,0 +1,87 @@
+package com.baeldung.postprocessor;
+
+import com.google.common.eventbus.EventBus;
+
+import java.util.function.BiConsumer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.aop.framework.Advised;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.FatalBeanException;
+import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionException;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+
+/**
+ * A {@link DestructionAwareBeanPostProcessor} which registers/un-registers subscribers to a Guava {@link EventBus}. The class must
+ * be annotated with {@link Subscriber} and each subscribing method must be annotated with
+ * {@link com.google.common.eventbus.Subscribe}.
+ */
+@SuppressWarnings("ALL")
+public class GuavaEventBusBeanPostProcessor implements DestructionAwareBeanPostProcessor {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+ private final SpelExpressionParser expressionParser = new SpelExpressionParser();
+
+ @Override
+ public void postProcessBeforeDestruction(final Object bean, final String beanName) throws BeansException {
+ this.process(bean, EventBus::unregister, "destruction");
+ }
+
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return true;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
+ this.process(bean, EventBus::register, "initialization");
+ return bean;
+ }
+
+ private void process(final Object bean, final BiConsumer consumer, final String action) {
+ Object proxy = this.getTargetObject(bean);
+ final Subscriber annotation = AnnotationUtils.getAnnotation(proxy.getClass(), Subscriber.class);
+ if (annotation == null)
+ return;
+ this.logger.info("{}: processing bean of type {} during {}", this.getClass().getSimpleName(), proxy.getClass().getName(),
+ action);
+ final String annotationValue = annotation.value();
+ try {
+ final Expression expression = this.expressionParser.parseExpression(annotationValue);
+ final Object value = expression.getValue();
+ if (!(value instanceof EventBus)) {
+ this.logger.error("{}: expression {} did not evaluate to an instance of EventBus for bean of type {}",
+ this.getClass().getSimpleName(), annotationValue, proxy.getClass().getSimpleName());
+ return;
+ }
+ final EventBus eventBus = (EventBus)value;
+ consumer.accept(eventBus, proxy);
+ } catch (ExpressionException ex) {
+ this.logger.error("{}: unable to parse/evaluate expression {} for bean of type {}", this.getClass().getSimpleName(),
+ annotationValue, proxy.getClass().getName());
+ }
+ }
+
+ private Object getTargetObject(Object proxy) throws BeansException {
+ if (AopUtils.isJdkDynamicProxy(proxy)) {
+ try {
+ return ((Advised)proxy).getTargetSource().getTarget();
+ } catch (Exception e) {
+ throw new FatalBeanException("Error getting target of JDK proxy", e);
+ }
+ }
+ return proxy;
+ }
+}
+
+
diff --git a/spring-core-4/src/main/java/com/baeldung/postprocessor/StockTrade.java b/spring-core-4/src/main/java/com/baeldung/postprocessor/StockTrade.java
new file mode 100644
index 0000000000..7711cf7101
--- /dev/null
+++ b/spring-core-4/src/main/java/com/baeldung/postprocessor/StockTrade.java
@@ -0,0 +1,34 @@
+package com.baeldung.postprocessor;
+
+import java.util.Date;
+
+public class StockTrade {
+
+ private final String symbol;
+ private final int quantity;
+ private final double price;
+ private final Date tradeDate;
+
+ public StockTrade(String symbol, int quantity, double price, Date tradeDate) {
+ this.symbol = symbol;
+ this.quantity = quantity;
+ this.price = price;
+ this.tradeDate = tradeDate;
+ }
+
+ public String getSymbol() {
+ return this.symbol;
+ }
+
+ public int getQuantity() {
+ return this.quantity;
+ }
+
+ public double getPrice() {
+ return this.price;
+ }
+
+ public Date getTradeDate() {
+ return this.tradeDate;
+ }
+}
diff --git a/spring-core-4/src/main/java/com/baeldung/postprocessor/StockTradeListener.java b/spring-core-4/src/main/java/com/baeldung/postprocessor/StockTradeListener.java
new file mode 100644
index 0000000000..bf34d66f24
--- /dev/null
+++ b/spring-core-4/src/main/java/com/baeldung/postprocessor/StockTradeListener.java
@@ -0,0 +1,7 @@
+package com.baeldung.postprocessor;
+
+@FunctionalInterface
+public interface StockTradeListener {
+
+ void stockTradePublished(StockTrade trade);
+}
diff --git a/spring-core-4/src/main/java/com/baeldung/postprocessor/StockTradePublisher.java b/spring-core-4/src/main/java/com/baeldung/postprocessor/StockTradePublisher.java
new file mode 100644
index 0000000000..bf339872d9
--- /dev/null
+++ b/spring-core-4/src/main/java/com/baeldung/postprocessor/StockTradePublisher.java
@@ -0,0 +1,36 @@
+package com.baeldung.postprocessor;
+
+import com.google.common.eventbus.AllowConcurrentEvents;
+import com.google.common.eventbus.Subscribe;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Subscriber
+public class StockTradePublisher {
+
+ private final Set stockTradeListeners = new HashSet<>();
+
+ public void addStockTradeListener(StockTradeListener listener) {
+ synchronized (this.stockTradeListeners) {
+ this.stockTradeListeners.add(listener);
+ }
+ }
+
+ public void removeStockTradeListener(StockTradeListener listener) {
+ synchronized (this.stockTradeListeners) {
+ this.stockTradeListeners.remove(listener);
+ }
+ }
+
+ @Subscribe
+ @AllowConcurrentEvents
+ private void handleNewStockTradeEvent(StockTrade trade) {
+ // publish to DB, send to PubNub, whatever you want here
+ final Set listeners;
+ synchronized (this.stockTradeListeners) {
+ listeners = new HashSet<>(this.stockTradeListeners);
+ }
+ listeners.forEach(li -> li.stockTradePublished(trade));
+ }
+}
diff --git a/spring-core-4/src/main/java/com/baeldung/postprocessor/Subscriber.java b/spring-core-4/src/main/java/com/baeldung/postprocessor/Subscriber.java
new file mode 100644
index 0000000000..bef38333d6
--- /dev/null
+++ b/spring-core-4/src/main/java/com/baeldung/postprocessor/Subscriber.java
@@ -0,0 +1,21 @@
+package com.baeldung.postprocessor;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation which indicates which Guava {@link com.google.common.eventbus.EventBus} a Spring bean wishes to subscribe to.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface Subscriber {
+
+ /**
+ * A SpEL expression which selects the {@link com.google.common.eventbus.EventBus}.
+ */
+ String value() default GlobalEventBus.GLOBAL_EVENT_BUS_EXPRESSION;
+}
diff --git a/spring-core-4/src/test/java/com/baeldung/postprocessor/PostProcessorConfiguration.java b/spring-core-4/src/test/java/com/baeldung/postprocessor/PostProcessorConfiguration.java
new file mode 100644
index 0000000000..b28e36663a
--- /dev/null
+++ b/spring-core-4/src/test/java/com/baeldung/postprocessor/PostProcessorConfiguration.java
@@ -0,0 +1,23 @@
+package com.baeldung.postprocessor;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class PostProcessorConfiguration {
+
+ @Bean
+ public GlobalEventBus eventBus() {
+ return GlobalEventBus.getInstance();
+ }
+
+ @Bean
+ public GuavaEventBusBeanPostProcessor eventBusBeanPostProcessor() {
+ return new GuavaEventBusBeanPostProcessor();
+ }
+
+ @Bean
+ public StockTradePublisher stockTradePublisher() {
+ return new StockTradePublisher();
+ }
+}
diff --git a/spring-core-4/src/test/java/com/baeldung/postprocessor/StockTradeIntegrationTest.java b/spring-core-4/src/test/java/com/baeldung/postprocessor/StockTradeIntegrationTest.java
new file mode 100644
index 0000000000..ae3cd968dc
--- /dev/null
+++ b/spring-core-4/src/test/java/com/baeldung/postprocessor/StockTradeIntegrationTest.java
@@ -0,0 +1,46 @@
+package com.baeldung.postprocessor;
+
+import java.time.Duration;
+import java.util.Date;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = {PostProcessorConfiguration.class})
+public class StockTradeIntegrationTest {
+
+ @Autowired
+ private StockTradePublisher stockTradePublisher;
+
+ @Test
+ public void givenValidConfig_whenTradePublished_thenTradeReceived() {
+ Date tradeDate = new Date();
+ StockTrade stockTrade = new StockTrade("AMZN", 100, 2483.52d, tradeDate);
+ AtomicBoolean assertionsPassed = new AtomicBoolean(false);
+ StockTradeListener listener = trade -> assertionsPassed.set(this.verifyExact(stockTrade, trade));
+ this.stockTradePublisher.addStockTradeListener(listener);
+ try {
+ GlobalEventBus.post(stockTrade);
+ await().atMost(Duration.ofSeconds(2L))
+ .untilAsserted(() -> assertThat(assertionsPassed.get()).isTrue());
+ } finally {
+ this.stockTradePublisher.removeStockTradeListener(listener);
+ }
+ }
+
+ private boolean verifyExact(StockTrade stockTrade, StockTrade trade) {
+ return Objects.equals(stockTrade.getSymbol(), trade.getSymbol())
+ && Objects.equals(stockTrade.getTradeDate(), trade.getTradeDate())
+ && stockTrade.getQuantity() == trade.getQuantity()
+ && stockTrade.getPrice() == trade.getPrice();
+ }
+}
diff --git a/spring-data-rest-querydsl/pom.xml b/spring-data-rest-querydsl/pom.xml
index c0ad43fe0b..5e47f4979e 100644
--- a/spring-data-rest-querydsl/pom.xml
+++ b/spring-data-rest-querydsl/pom.xml
@@ -9,9 +9,9 @@
com.baeldung
- parent-boot-1
+ parent-boot-2
0.0.1-SNAPSHOT
- ../parent-boot-1
+ ../parent-boot-2
diff --git a/spring-data-rest-querydsl/src/main/java/com/baeldung/controller/repository/AddressRepository.java b/spring-data-rest-querydsl/src/main/java/com/baeldung/controller/repository/AddressRepository.java
index 2e88820c98..476a11ea6b 100644
--- a/spring-data-rest-querydsl/src/main/java/com/baeldung/controller/repository/AddressRepository.java
+++ b/spring-data-rest-querydsl/src/main/java/com/baeldung/controller/repository/AddressRepository.java
@@ -5,13 +5,13 @@ import com.baeldung.entity.QAddress;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.core.types.dsl.StringPath;
import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.querydsl.QueryDslPredicateExecutor;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
import org.springframework.data.querydsl.binding.QuerydslBindings;
import org.springframework.data.querydsl.binding.SingleValueBinding;
public interface AddressRepository
- extends JpaRepository, QueryDslPredicateExecutor, QuerydslBinderCustomizer {
+ extends JpaRepository, QuerydslPredicateExecutor, QuerydslBinderCustomizer {
@Override
default void customize(final QuerydslBindings bindings, final QAddress root) {
bindings.bind(String.class).first((SingleValueBinding) StringExpression::eq);
diff --git a/spring-data-rest-querydsl/src/main/java/com/baeldung/controller/repository/UserRepository.java b/spring-data-rest-querydsl/src/main/java/com/baeldung/controller/repository/UserRepository.java
index 98ff2ac5e3..b5f32b3624 100644
--- a/spring-data-rest-querydsl/src/main/java/com/baeldung/controller/repository/UserRepository.java
+++ b/spring-data-rest-querydsl/src/main/java/com/baeldung/controller/repository/UserRepository.java
@@ -5,13 +5,13 @@ import com.baeldung.entity.User;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.core.types.dsl.StringPath;
import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.querydsl.QueryDslPredicateExecutor;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
import org.springframework.data.querydsl.binding.QuerydslBindings;
import org.springframework.data.querydsl.binding.SingleValueBinding;
public interface UserRepository
- extends JpaRepository, QueryDslPredicateExecutor, QuerydslBinderCustomizer {
+ extends JpaRepository, QuerydslPredicateExecutor, QuerydslBinderCustomizer {
@Override
default void customize(final QuerydslBindings bindings, final QUser root) {
bindings.bind(String.class).first((SingleValueBinding) StringExpression::eq);
diff --git a/spring-data-rest-querydsl/src/test/java/com/baeldung/springdatarestquerydsl/IntegrationTest.java b/spring-data-rest-querydsl/src/test/java/com/baeldung/springdatarestquerydsl/IntegrationTest.java
index 2d3dbc4c74..ad19c441b6 100644
--- a/spring-data-rest-querydsl/src/test/java/com/baeldung/springdatarestquerydsl/IntegrationTest.java
+++ b/spring-data-rest-querydsl/src/test/java/com/baeldung/springdatarestquerydsl/IntegrationTest.java
@@ -13,8 +13,6 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
-import java.nio.charset.Charset;
-
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -24,7 +22,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public class IntegrationTest {
final MediaType contentType =
- new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
+ new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype());
@Autowired private WebApplicationContext webApplicationContext;
diff --git a/spring-data-rest-querydsl/src/test/java/com/baeldung/springdatarestquerydsl/QuerydslIntegrationTest.java b/spring-data-rest-querydsl/src/test/java/com/baeldung/springdatarestquerydsl/QuerydslIntegrationTest.java
index 11e5ffca05..768d28347b 100644
--- a/spring-data-rest-querydsl/src/test/java/com/baeldung/springdatarestquerydsl/QuerydslIntegrationTest.java
+++ b/spring-data-rest-querydsl/src/test/java/com/baeldung/springdatarestquerydsl/QuerydslIntegrationTest.java
@@ -13,8 +13,6 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
-import java.nio.charset.Charset;
-
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -26,7 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public class QuerydslIntegrationTest {
final MediaType contentType =
- new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
+ new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype());
@Autowired
private WebApplicationContext webApplicationContext;
diff --git a/spring-jenkins-pipeline/pom.xml b/spring-jenkins-pipeline/pom.xml
index aa6008162c..38d4ed15de 100644
--- a/spring-jenkins-pipeline/pom.xml
+++ b/spring-jenkins-pipeline/pom.xml
@@ -10,9 +10,9 @@
com.baeldung
- parent-boot-1
+ parent-boot-2
0.0.1-SNAPSHOT
- ../parent-boot-1
+ ../parent-boot-2
diff --git a/spring-jenkins-pipeline/src/test/java/com/baeldung/SomeIntegrationTest.java b/spring-jenkins-pipeline/src/test/java/com/baeldung/SomeIntegrationTest.java
index 477a7d2adb..9033d10c5d 100644
--- a/spring-jenkins-pipeline/src/test/java/com/baeldung/SomeIntegrationTest.java
+++ b/spring-jenkins-pipeline/src/test/java/com/baeldung/SomeIntegrationTest.java
@@ -13,7 +13,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertNotEquals;
@RunWith(SpringJUnit4ClassRunner.class)
-@SpringBootTest(classes = {SpringJenkinsPipelineApplication.class, TestMongoConfig.class })
+@SpringBootTest(classes = {SpringJenkinsPipelineApplication.class})
public class SomeIntegrationTest {
@Autowired
private StudentRepository studentRepository;
diff --git a/spring-jenkins-pipeline/src/test/java/com/baeldung/TestMongoConfig.java b/spring-jenkins-pipeline/src/test/java/com/baeldung/TestMongoConfig.java
deleted file mode 100644
index a85491cf7e..0000000000
--- a/spring-jenkins-pipeline/src/test/java/com/baeldung/TestMongoConfig.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.baeldung;
-
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-@EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
-public class TestMongoConfig {
-
-}
\ No newline at end of file
diff --git a/spring-jinq/pom.xml b/spring-jinq/pom.xml
index 29fc3605d7..073808823c 100644
--- a/spring-jinq/pom.xml
+++ b/spring-jinq/pom.xml
@@ -9,9 +9,9 @@
com.baeldung
- parent-boot-1
+ parent-boot-2
0.0.1-SNAPSHOT
- ../parent-boot-1
+ ../parent-boot-2
@@ -31,6 +31,17 @@
org.hibernate
hibernate-entitymanager
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ net.bytebuddy
+ byte-buddy-dep
+ ${bytebuddy.version}
+
@@ -61,7 +72,8 @@
- 1.8.22
+ 1.8.29
+ 1.10.10
diff --git a/spring-jinq/src/main/resources/application.properties b/spring-jinq/src/main/resources/application.properties
index dc73bed0c5..c9440b3b45 100644
--- a/spring-jinq/src/main/resources/application.properties
+++ b/spring-jinq/src/main/resources/application.properties
@@ -1,4 +1,4 @@
-spring.datasource.url=jdbc:h2:~/jinq
+spring.datasource.url=jdbc:h2:~/jinq;;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=sa
spring.datasource.password=
diff --git a/spring-mvc-webflow/pom.xml b/spring-mvc-webflow/pom.xml
index 5a6856385c..22ae3c913d 100644
--- a/spring-mvc-webflow/pom.xml
+++ b/spring-mvc-webflow/pom.xml
@@ -72,6 +72,24 @@
+
+ org.apache.tomee.maven
+ tomee-maven-plugin
+ 8.0.1
+
+ 8080
+ spring-mvc-webflow
+ true
+ plume
+
+
+ .class
+
+
+ -Xmx2048m -XX:PermSize=256m -Dtomee.serialization.class.blacklist=- -Dtomee.serialization.class.whitelist=*
+ true
+
+
org.apache.maven.plugins
maven-war-plugin
diff --git a/spring-security-modules/pom.xml b/spring-security-modules/pom.xml
index 7ce33dd3e3..60a662781f 100644
--- a/spring-security-modules/pom.xml
+++ b/spring-security-modules/pom.xml
@@ -15,6 +15,7 @@
spring-security-acl
+ spring-security-auth0
spring-security-angular/server
spring-security-cache-control
spring-security-core
diff --git a/spring-security-modules/spring-security-auth0/pom.xml b/spring-security-modules/spring-security-auth0/pom.xml
new file mode 100644
index 0000000000..0bd879a40b
--- /dev/null
+++ b/spring-security-modules/spring-security-auth0/pom.xml
@@ -0,0 +1,75 @@
+
+
+ 4.0.0
+ spring-security-auth0
+ 1.0-SNAPSHOT
+ spring-security-auth0
+ war
+
+
+ com.baeldung
+ parent-boot-2
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-2
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.security
+ spring-security-core
+
+
+ org.springframework.security
+ spring-security-oauth2-resource-server
+
+
+ com.auth0
+ mvc-auth-commons
+ ${mvc-auth-commons.version}
+
+
+ org.json
+ json
+ ${json.version}
+
+
+
+
+ spring-security-auth0
+
+
+ src/main/resources
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+
+
+
+
+ repackage
+
+
+
+
+
+
+
+
+ 20190722
+ 1.2.0
+
+
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/Application.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/Application.java
new file mode 100644
index 0000000000..42f8d946b5
--- /dev/null
+++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/Application.java
@@ -0,0 +1,13 @@
+package com.baeldung.auth0;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/AuthConfig.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/AuthConfig.java
new file mode 100644
index 0000000000..69cf8b3071
--- /dev/null
+++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/AuthConfig.java
@@ -0,0 +1,114 @@
+package com.baeldung.auth0;
+
+import java.io.UnsupportedEncodingException;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+
+import com.auth0.AuthenticationController;
+import com.baeldung.auth0.controller.LogoutController;
+import com.auth0.jwk.JwkProvider;
+import com.auth0.jwk.JwkProviderBuilder;
+
+@Configuration
+@EnableWebSecurity
+public class AuthConfig extends WebSecurityConfigurerAdapter {
+
+ @Value(value = "${com.auth0.domain}")
+ private String domain;
+
+ @Value(value = "${com.auth0.clientId}")
+ private String clientId;
+
+ @Value(value = "${com.auth0.clientSecret}")
+ private String clientSecret;
+
+ @Value(value = "${com.auth0.managementApi.clientId}")
+ private String managementApiClientId;
+
+ @Value(value = "${com.auth0.managementApi.clientSecret}")
+ private String managementApiClientSecret;
+
+ @Value(value = "${com.auth0.managementApi.grantType}")
+ private String grantType;
+
+ @Bean
+ public LogoutSuccessHandler logoutSuccessHandler() {
+ return new LogoutController();
+ }
+
+ @Bean
+ public AuthenticationController authenticationController() throws UnsupportedEncodingException {
+ JwkProvider jwkProvider = new JwkProviderBuilder(domain).build();
+ return AuthenticationController.newBuilder(domain, clientId, clientSecret)
+ .withJwkProvider(jwkProvider)
+ .build();
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.csrf().disable();
+ http
+ .authorizeRequests()
+ .antMatchers("/callback", "/login", "/").permitAll()
+ .anyRequest().authenticated()
+ .and()
+ .formLogin()
+ .loginPage("/login")
+ .and()
+ .logout().logoutSuccessHandler(logoutSuccessHandler()).permitAll();
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public String getClientSecret() {
+ return clientSecret;
+ }
+
+ public String getManagementApiClientId() {
+ return managementApiClientId;
+ }
+
+ public String getManagementApiClientSecret() {
+ return managementApiClientSecret;
+ }
+
+ public String getGrantType() {
+ return grantType;
+ }
+
+ public String getUserInfoUrl() {
+ return "https://" + getDomain() + "/userinfo";
+ }
+
+ public String getUsersUrl() {
+ return "https://" + getDomain() + "/api/v2/users";
+ }
+
+ public String getUsersByEmailUrl() {
+ return "https://" + getDomain() + "/api/v2/users-by-email?email=";
+ }
+
+ public String getLogoutUrl() {
+ return "https://" + getDomain() +"/v2/logout";
+ }
+
+ public String getContextPath(HttpServletRequest request) {
+ String path = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
+ return path;
+ }
+}
diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/AuthController.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/AuthController.java
new file mode 100644
index 0000000000..48d09db155
--- /dev/null
+++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/AuthController.java
@@ -0,0 +1,77 @@
+package com.baeldung.auth0.controller;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.client.RestTemplate;
+
+import com.auth0.AuthenticationController;
+import com.auth0.IdentityVerificationException;
+import com.auth0.Tokens;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.baeldung.auth0.AuthConfig;
+
+@Controller
+public class AuthController {
+
+ @Autowired
+ private AuthenticationController authenticationController;
+
+ @Autowired
+ private AuthConfig config;
+
+ private static final String AUTH0_TOKEN_URL = "https://dev-example.auth0.com/oauth/token";
+
+ @GetMapping(value = "/login")
+ protected void login(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ String redirectUri = config.getContextPath(request) + "/callback";
+ String authorizeUrl = authenticationController.buildAuthorizeUrl(request, response, redirectUri)
+ .withScope("openid email")
+ .build();
+ response.sendRedirect(authorizeUrl);
+ }
+
+ @GetMapping(value="/callback")
+ public void callback(HttpServletRequest request, HttpServletResponse response) throws IOException, IdentityVerificationException {
+ Tokens tokens = authenticationController.handle(request, response);
+
+ DecodedJWT jwt = JWT.decode(tokens.getIdToken());
+ TestingAuthenticationToken authToken2 = new TestingAuthenticationToken(jwt.getSubject(), jwt.getToken());
+ authToken2.setAuthenticated(true);
+
+ SecurityContextHolder.getContext().setAuthentication(authToken2);
+ response.sendRedirect(config.getContextPath(request) + "/");
+ }
+
+ public String getManagementApiToken() {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+
+ JSONObject requestBody = new JSONObject();
+ requestBody.put("client_id", config.getManagementApiClientId());
+ requestBody.put("client_secret", config.getManagementApiClientSecret());
+ requestBody.put("audience", "https://dev-example.auth0.com/api/v2/");
+ requestBody.put("grant_type", config.getGrantType());
+
+ HttpEntity request = new HttpEntity(requestBody.toString(), headers);
+
+ RestTemplate restTemplate = new RestTemplate();
+ HashMap result = restTemplate.postForObject(AUTH0_TOKEN_URL, request, HashMap.class);
+
+ return result.get("access_token");
+ }
+
+}
diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/HomeController.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/HomeController.java
new file mode 100644
index 0000000000..8a4e650846
--- /dev/null
+++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/HomeController.java
@@ -0,0 +1,37 @@
+package com.baeldung.auth0.controller;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.interfaces.DecodedJWT;
+
+@Controller
+public class HomeController {
+
+ @GetMapping(value = "/")
+ @ResponseBody
+ public String home(HttpServletRequest request, HttpServletResponse response, final Authentication authentication) throws IOException {
+
+ if (authentication!= null && authentication instanceof TestingAuthenticationToken) {
+ TestingAuthenticationToken token = (TestingAuthenticationToken) authentication;
+
+ DecodedJWT jwt = JWT.decode(token.getCredentials().toString());
+ String email = jwt.getClaims().get("email").asString();
+
+ return "Welcome, " + email + "!";
+ } else {
+ response.sendRedirect("http://localhost:8080/login");
+ return null;
+ }
+ }
+
+}
diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/LogoutController.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/LogoutController.java
new file mode 100755
index 0000000000..d508fe2c44
--- /dev/null
+++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/LogoutController.java
@@ -0,0 +1,35 @@
+package com.baeldung.auth0.controller;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+import org.springframework.stereotype.Controller;
+
+import com.baeldung.auth0.AuthConfig;
+
+@Controller
+public class LogoutController implements LogoutSuccessHandler {
+
+ @Autowired
+ private AuthConfig config;
+
+ @Override
+ public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse res, Authentication authentication) {
+ if (req.getSession() != null) {
+ req.getSession().invalidate();
+ }
+ String returnTo = config.getContextPath(req);
+ String logoutUrl = config.getLogoutUrl() + "?client_id=" + config.getClientId() + "&returnTo=" +returnTo;
+ try {
+ res.sendRedirect(logoutUrl);
+ } catch(IOException e){
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/UserController.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/UserController.java
new file mode 100644
index 0000000000..86601a06d3
--- /dev/null
+++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/UserController.java
@@ -0,0 +1,57 @@
+package com.baeldung.auth0.controller;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.auth0.IdentityVerificationException;
+import com.baeldung.auth0.AuthConfig;
+import com.baeldung.auth0.service.ApiService;
+
+@Controller
+public class UserController {
+
+ @Autowired
+ private ApiService apiService;
+
+ @Autowired
+ private AuthConfig config;
+
+ @GetMapping(value="/users")
+ @ResponseBody
+ public ResponseEntity users(HttpServletRequest request, HttpServletResponse response) throws IOException, IdentityVerificationException {
+ ResponseEntity result = apiService.getCall(config.getUsersUrl());
+ return result;
+ }
+
+ @GetMapping(value = "/userByEmail")
+ @ResponseBody
+ public ResponseEntity userByEmail(HttpServletResponse response, @RequestParam String email) {
+ ResponseEntity result = apiService.getCall(config.getUsersByEmailUrl()+email);
+ return result;
+ }
+
+ @GetMapping(value = "/createUser")
+ @ResponseBody
+ public ResponseEntity createUser(HttpServletResponse response) {
+ JSONObject request = new JSONObject();
+ request.put("email", "norman.lewis@email.com");
+ request.put("given_name", "Norman");
+ request.put("family_name", "Lewis");
+ request.put("connection", "Username-Password-Authentication");
+ request.put("password", "Pa33w0rd");
+
+ ResponseEntity result = apiService.postCall(config.getUsersUrl(), request.toString());
+ return result;
+ }
+
+}
diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/service/ApiService.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/service/ApiService.java
new file mode 100644
index 0000000000..0d8263ae19
--- /dev/null
+++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/service/ApiService.java
@@ -0,0 +1,44 @@
+package com.baeldung.auth0.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import com.baeldung.auth0.controller.AuthController;
+
+@Service
+public class ApiService {
+
+ @Autowired
+ private AuthController controller;
+
+ public ResponseEntity getCall(String url) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.set("Authorization", "Bearer "+controller.getManagementApiToken());
+
+ HttpEntity entity = new HttpEntity(headers);
+ RestTemplate restTemplate = new RestTemplate();
+ ResponseEntity result = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
+
+ return result;
+ }
+
+ public ResponseEntity postCall(String url, String requestBody) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.set("Authorization", "Bearer "+controller.getManagementApiToken());
+
+ HttpEntity request = new HttpEntity(requestBody, headers);
+ RestTemplate restTemplate = new RestTemplate();
+ ResponseEntity result = restTemplate.postForEntity(url, request, String.class);
+
+ return result;
+ }
+
+}
diff --git a/spring-security-modules/spring-security-auth0/src/main/resources/application.properties b/spring-security-modules/spring-security-auth0/src/main/resources/application.properties
new file mode 100644
index 0000000000..45492c5c00
--- /dev/null
+++ b/spring-security-modules/spring-security-auth0/src/main/resources/application.properties
@@ -0,0 +1,7 @@
+com.auth0.domain: dev-example.auth0.com
+com.auth0.clientId: exampleClientId
+com.auth0.clientSecret: exampleClientSecret
+
+com.auth0.managementApi.clientId: exampleManagementApiClientId
+com.auth0.managementApi.clientSecret: exampleManagementApiClientSecret
+com.auth0.managementApi.grantType: client_credentials
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-mvc-boot-1/pom.xml b/spring-security-modules/spring-security-mvc-boot-1/pom.xml
index 7ad18376ec..b00b7bab32 100644
--- a/spring-security-modules/spring-security-mvc-boot-1/pom.xml
+++ b/spring-security-modules/spring-security-mvc-boot-1/pom.xml
@@ -106,6 +106,10 @@
${ehcache-core.version}
jar
+
+ net.bytebuddy
+ byte-buddy
+
diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/roles/voter/VoterApplication.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/roles/voter/VoterApplication.java
index d3e0652ae9..148f9c17b1 100644
--- a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/roles/voter/VoterApplication.java
+++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/roles/voter/VoterApplication.java
@@ -7,7 +7,7 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@EnableAutoConfiguration
-@ComponentScan(basePackages = {"com.baeldung.voter"})
+@ComponentScan(basePackages = {"com.baeldung.roles.voter"})
public class VoterApplication {
public static void main(String[] args) {
diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/roles/voter/VoterMvcConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/roles/voter/VoterMvcConfig.java
index f11a4ae06c..402065129f 100644
--- a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/roles/voter/VoterMvcConfig.java
+++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/roles/voter/VoterMvcConfig.java
@@ -12,6 +12,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class VoterMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
- registry.addViewController("/").setViewName("private");
+ registry.addViewController("/private").setViewName("private");
}
}
diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/roles/voter/WebSecurityConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/roles/voter/WebSecurityConfig.java
index 8a0f438b49..1a6d1b8235 100644
--- a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/roles/voter/WebSecurityConfig.java
+++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/roles/voter/WebSecurityConfig.java
@@ -34,7 +34,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// @formatter: off
http
// needed so our login could work
- .csrf().disable().authorizeRequests().anyRequest().authenticated().accessDecisionManager(accessDecisionManager()).antMatchers("/").hasAnyRole("ROLE_ADMIN", "ROLE_USER").and().formLogin().permitAll().and().logout().permitAll()
+ .csrf().disable().authorizeRequests().anyRequest().authenticated().accessDecisionManager(accessDecisionManager()).and().formLogin().permitAll().and().logout().permitAll()
.deleteCookies("JSESSIONID").logoutSuccessUrl("/login");
// @formatter: on
}
diff --git a/spring-thymeleaf-3/src/test/java/com/baeldung/thymeleaf/currencies/CurrenciesControllerIntegrationTest.java b/spring-thymeleaf-3/src/test/java/com/baeldung/thymeleaf/currencies/CurrenciesControllerIntegrationTest.java
index 02bf8a9ee0..c1e3cf7458 100644
--- a/spring-thymeleaf-3/src/test/java/com/baeldung/thymeleaf/currencies/CurrenciesControllerIntegrationTest.java
+++ b/spring-thymeleaf-3/src/test/java/com/baeldung/thymeleaf/currencies/CurrenciesControllerIntegrationTest.java
@@ -27,7 +27,7 @@ public class CurrenciesControllerIntegrationTest {
.header("Accept-Language", "es-ES")
.param("amount", "10032.5"))
.andExpect(status().isOk())
- .andExpect(content().string(containsString("10.032,50 €")));
+ .andExpect(content().string(containsString("10.032,50")));
}
@Test
@@ -42,10 +42,10 @@ public class CurrenciesControllerIntegrationTest {
@Test
public void whenCallCurrencyWithRomanianLocaleWithArrays_ThenReturnLocaleCurrencies() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/currency")
- .header("Accept-Language", "ro-RO")
+ .header("Accept-Language", "en-GB")
.param("amountList", "10", "20", "30"))
.andExpect(status().isOk())
- .andExpect(content().string(containsString("10,00 RON, 20,00 RON, 30,00 RON")));
+ .andExpect(content().string(containsString("£10.00, £20.00, £30.00")));
}
@Test
diff --git a/testing-modules/testing-libraries/src/test/java/com/baeldung/mutation/PalindromeUnitTest.java b/testing-modules/testing-libraries/src/test/java/com/baeldung/mutation/PalindromeUnitTest.java
index cb4830a6fb..207077158e 100644
--- a/testing-modules/testing-libraries/src/test/java/com/baeldung/mutation/PalindromeUnitTest.java
+++ b/testing-modules/testing-libraries/src/test/java/com/baeldung/mutation/PalindromeUnitTest.java
@@ -11,13 +11,13 @@ public class PalindromeUnitTest {
@Test
public void whenEmptyString_thanAccept() {
Palindrome palindromeTester = new Palindrome();
- assertTrue(palindromeTester.isPalindrome("noon"));
+ assertTrue(palindromeTester.isPalindrome(""));
}
@Test
- public void whenPalindrom_thanAccept() {
- Palindrome palindromeTester = new Palindrome();
- assertTrue(palindromeTester.isPalindrome("noon"));
+ public void whenPalindrom_thanAccept() {
+ Palindrome palindromeTester = new Palindrome();
+ assertTrue(palindromeTester.isPalindrome("noon"));
}
@Test
diff --git a/testing-modules/testng/pom.xml b/testing-modules/testng/pom.xml
index 601b152144..c4a1284b0e 100644
--- a/testing-modules/testng/pom.xml
+++ b/testing-modules/testng/pom.xml
@@ -42,7 +42,7 @@
- 6.10
+ 7.1.0
\ No newline at end of file