+
+
+
diff --git a/javax-servlets-2/src/main/webapp/WEB-INF/user.check/login.jsp b/javax-servlets-2/src/main/webapp/WEB-INF/user.check/login.jsp
new file mode 100644
index 0000000000..19a857585d
--- /dev/null
+++ b/javax-servlets-2/src/main/webapp/WEB-INF/user.check/login.jsp
@@ -0,0 +1,33 @@
+<%@ page contentType="text/html;charset=UTF-8" session="false"%>
+<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c"%>
+
+
+
+ login
+
+
+
+
+
diff --git a/javax-servlets-2/src/test/java/com/baeldung/user/check/UserCheckServletLiveTest.java b/javax-servlets-2/src/test/java/com/baeldung/user/check/UserCheckServletLiveTest.java
new file mode 100644
index 0000000000..42858d61e7
--- /dev/null
+++ b/javax-servlets-2/src/test/java/com/baeldung/user/check/UserCheckServletLiveTest.java
@@ -0,0 +1,98 @@
+package com.baeldung.user.check;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.LaxRedirectStrategy;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class UserCheckServletLiveTest {
+ private static final String BASE_URL = "http://localhost:8080/javax-servlets-2/user-check";
+
+ @Mock
+ HttpServletRequest request;
+
+ @Mock
+ HttpServletResponse response;
+
+ private CloseableHttpClient buildClient() {
+ return HttpClientBuilder.create()
+ .setRedirectStrategy(new LaxRedirectStrategy())
+ .build();
+ }
+
+ @Test
+ public void whenCorrectCredentials_thenLoginSucceeds() throws Exception {
+ try (CloseableHttpClient client = buildClient()) {
+ HttpPost post = new HttpPost(BASE_URL + "/login");
+
+ List form = new ArrayList<>();
+ form.add(new BasicNameValuePair("name", "admin"));
+ form.add(new BasicNameValuePair("password", "password"));
+
+ post.setEntity(new UrlEncodedFormEntity(form));
+ try (CloseableHttpResponse response = client.execute(post)) {
+ String body = EntityUtils.toString(response.getEntity());
+
+ assertTrue(response.getStatusLine()
+ .getStatusCode() == 200);
+
+ assertTrue(body.contains("login success"));
+ }
+ }
+ }
+
+ @Test
+ public void whenIncorrectCredentials_thenLoginFails() throws Exception {
+ try (CloseableHttpClient client = buildClient()) {
+ HttpPost post = new HttpPost(BASE_URL + "/login");
+
+ List form = new ArrayList<>();
+ form.add(new BasicNameValuePair("name", "admin"));
+ form.add(new BasicNameValuePair("password", "invalid"));
+
+ post.setEntity(new UrlEncodedFormEntity(form));
+ try (CloseableHttpResponse response = client.execute(post)) {
+ String body = EntityUtils.toString(response.getEntity());
+
+ assertTrue(response.getStatusLine()
+ .getStatusCode() == 401);
+
+ assertTrue(body.contains("invalid login"));
+ }
+ }
+ }
+
+ @Test
+ public void whenNotLoggedIn_thenRedirectedToLoginPage() throws Exception {
+ try (CloseableHttpClient client = buildClient()) {
+ HttpGet get = new HttpGet(BASE_URL + "/home");
+
+ try (CloseableHttpResponse response = client.execute(get)) {
+ String body = EntityUtils.toString(response.getEntity());
+
+ assertTrue(response.getStatusLine()
+ .getStatusCode() == 401);
+
+ assertTrue(body.contains("redirected to login"));
+ }
+ }
+ }
+}
diff --git a/javax-servlets/src/main/webapp/sample.txt b/javax-servlets/src/main/webapp/WEB-INF/sample.txt
similarity index 100%
rename from javax-servlets/src/main/webapp/sample.txt
rename to javax-servlets/src/main/webapp/WEB-INF/sample.txt
diff --git a/javaxval/README.md b/javaxval/README.md
index cc237e1e59..24c2cf98c5 100644
--- a/javaxval/README.md
+++ b/javaxval/README.md
@@ -11,3 +11,4 @@ This module contains articles about Bean Validation.
- [Grouping Javax Validation Constraints](https://www.baeldung.com/javax-validation-groups)
- [Validations for Enum Types](https://www.baeldung.com/javax-validations-enums)
- [Guide to ParameterMessageInterpolator](https://www.baeldung.com/hibernate-parametermessageinterpolator)
+- [Hibernate Validator Annotation Processor in Depth](https://www.baeldung.com/hibernate-validator-annotation-processor)
diff --git a/javaxval/pom.xml b/javaxval/pom.xml
index 57369c6f52..e6ecee6cfb 100644
--- a/javaxval/pom.xml
+++ b/javaxval/pom.xml
@@ -15,7 +15,7 @@
- org.hibernate
+ org.hibernate.validatorhibernate-validator${hibernate-validator.version}
@@ -36,8 +36,21 @@
+
+
+
6.0.13.Final
+ 6.2.0.Final
+ 3.6.1
+ 1.8
+ 1.83.0.05.0.2.RELEASE
diff --git a/javaxval/src/main/java/com/baeldung/javaxval/hibernate/validator/ap/Message.java b/javaxval/src/main/java/com/baeldung/javaxval/hibernate/validator/ap/Message.java
new file mode 100644
index 0000000000..55d6dafad7
--- /dev/null
+++ b/javaxval/src/main/java/com/baeldung/javaxval/hibernate/validator/ap/Message.java
@@ -0,0 +1,95 @@
+package com.baeldung.javaxval.hibernate.validator.ap;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Past;
+import java.util.List;
+import java.util.Optional;
+
+public class Message {
+
+ @NotNull(message = "Content cannot be null")
+ private String content;
+
+ private boolean isDelivered;
+
+ private List<@NotBlank String> recipients;
+
+ // uncomment in order to trigger AP annotation detection
+ // The annotation @Past is disallowed for this data type.
+ // @Past
+ private String createdAt;
+
+ public String getContent() {
+ return content;
+ }
+
+ public Message(String content, boolean isDelivered, List<@NotBlank String> recipients, String createdAt) {
+ this.content = content;
+ this.isDelivered = isDelivered;
+ this.recipients = recipients;
+ this.createdAt = createdAt;
+ }
+
+ // uncomment in order to trigger AP annotation detection
+ // The annotation @Min is disallowed for the return type of this method.
+ // @Min(3)
+ public boolean broadcast() {
+ // setup a logic
+ // to send to recipients
+ return true;
+ }
+
+ // uncomment in order to trigger AP annotation detection
+ // Void methods may not be annotated with constraint annotations.
+ // @NotNull
+ public void archive() {
+ // archive the message
+ }
+
+ // uncomment in order to trigger AP annotation detection
+ // Constraint annotations must not be specified at methods, which are no valid JavaBeans getter methods.
+ // NOTE: add -AmethodConstraintsSupported=false to compiler args before
+ // @AssertTrue
+ public boolean delete() {
+ // delete the message
+ return false;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public boolean isDelivered() {
+ return isDelivered;
+ }
+
+ public void setDelivered(boolean delivered) {
+ isDelivered = delivered;
+ }
+
+ public List getRecipients() {
+ return recipients;
+ }
+
+ public void setRecipients(List recipients) {
+ this.recipients = recipients;
+ }
+
+ public void setCreatedAt(String createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public String getName() {
+ return content;
+ }
+
+ public void setName(String content) {
+ this.content = content;
+ }
+
+ public Optional<@Past String> getCreatedAt() {
+ return Optional.ofNullable(createdAt);
+ }
+
+}
diff --git a/jaxb/src/main/java/com/baeldung/jaxb/gen/ObjectFactory.java b/jaxb/src/main/java/com/baeldung/jaxb/gen/ObjectFactory.java
index 26cd5814ac..0a3da677ce 100644
--- a/jaxb/src/main/java/com/baeldung/jaxb/gen/ObjectFactory.java
+++ b/jaxb/src/main/java/com/baeldung/jaxb/gen/ObjectFactory.java
@@ -1,48 +1,48 @@
-
-package com.baeldung.jaxb.gen;
-
-import javax.xml.bind.annotation.XmlRegistry;
-
-
-/**
- * This object contains factory methods for each
- * Java content interface and Java element interface
- * generated in the com.baeldung.jaxb.gen package.
- *
An ObjectFactory allows you to programatically
- * construct new instances of the Java representation
- * for XML content. The Java representation of XML
- * content can consist of schema derived interfaces
- * and classes representing the binding of schema
- * type definitions, element declarations and model
- * groups. Factory methods for each of these are
- * provided in this class.
- *
- */
-@XmlRegistry
-public class ObjectFactory {
-
-
- /**
- * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.baeldung.jaxb.gen
- *
- */
- public ObjectFactory() {
- }
-
- /**
- * Create an instance of {@link UserRequest }
- *
- */
- public UserRequest createUserRequest() {
- return new UserRequest();
- }
-
- /**
- * Create an instance of {@link UserResponse }
- *
- */
- public UserResponse createUserResponse() {
- return new UserResponse();
- }
-
-}
+
+package com.baeldung.jaxb.gen;
+
+import javax.xml.bind.annotation.XmlRegistry;
+
+
+/**
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.baeldung.jaxb.gen package.
+ *
An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+@XmlRegistry
+public class ObjectFactory {
+
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.baeldung.jaxb.gen
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link UserRequest }
+ *
+ */
+ public UserRequest createUserRequest() {
+ return new UserRequest();
+ }
+
+ /**
+ * Create an instance of {@link UserResponse }
+ *
+ */
+ public UserResponse createUserResponse() {
+ return new UserResponse();
+ }
+
+}
diff --git a/jaxb/src/main/java/com/baeldung/jaxb/gen/UserRequest.java b/jaxb/src/main/java/com/baeldung/jaxb/gen/UserRequest.java
index 4cfbeb8d46..1c1abc61a6 100644
--- a/jaxb/src/main/java/com/baeldung/jaxb/gen/UserRequest.java
+++ b/jaxb/src/main/java/com/baeldung/jaxb/gen/UserRequest.java
@@ -1,87 +1,87 @@
-
-package com.baeldung.jaxb.gen;
-
-import java.io.Serializable;
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlType;
-
-
-/**
- *
Java class for UserRequest complex type.
- *
- *
The following schema fragment specifies the expected content contained within this class.
- *
- *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "UserResponse", propOrder = {
+ "id",
+ "name",
+ "gender",
+ "created"
+})
+@XmlRootElement(name = "userResponse")
+public class UserResponse
+ implements Serializable
+{
+
+ private final static long serialVersionUID = -1L;
+ protected int id;
+ @XmlElement(required = true)
+ protected String name;
+ @XmlElement(required = true)
+ protected String gender;
+ @XmlElement(required = true, type = String.class)
+ @XmlJavaTypeAdapter(Adapter1 .class)
+ @XmlSchemaType(name = "dateTime")
+ protected Calendar created;
+
+ /**
+ * Gets the value of the id property.
+ *
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Sets the value of the id property.
+ *
+ */
+ public void setId(int value) {
+ this.id = value;
+ }
+
+ /**
+ * Gets the value of the name property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the name property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setName(String value) {
+ this.name = value;
+ }
+
+ /**
+ * Gets the value of the gender property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getGender() {
+ return gender;
+ }
+
+ /**
+ * Sets the value of the gender property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setGender(String value) {
+ this.gender = value;
+ }
+
+ /**
+ * Gets the value of the created property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public Calendar getCreated() {
+ return created;
+ }
+
+ /**
+ * Sets the value of the created property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setCreated(Calendar value) {
+ this.created = value;
+ }
+
+}
diff --git a/jaxb/src/main/java/com/baeldung/jaxb/gen/package-info.java b/jaxb/src/main/java/com/baeldung/jaxb/gen/package-info.java
index 6384eab27f..639d00179c 100644
--- a/jaxb/src/main/java/com/baeldung/jaxb/gen/package-info.java
+++ b/jaxb/src/main/java/com/baeldung/jaxb/gen/package-info.java
@@ -1,2 +1,2 @@
-@javax.xml.bind.annotation.XmlSchema(namespace = "http://www.baeldung.com/jaxb/gen", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
-package com.baeldung.jaxb.gen;
+@javax.xml.bind.annotation.XmlSchema(namespace = "http://www.baeldung.com/jaxb/gen", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
+package com.baeldung.jaxb.gen;
diff --git a/jaxb/src/main/java/org/w3/_2001/xmlschema/Adapter1.java b/jaxb/src/main/java/org/w3/_2001/xmlschema/Adapter1.java
index b4865b5510..54b3c360dc 100644
--- a/jaxb/src/main/java/org/w3/_2001/xmlschema/Adapter1.java
+++ b/jaxb/src/main/java/org/w3/_2001/xmlschema/Adapter1.java
@@ -1,23 +1,23 @@
-
-package org.w3._2001.xmlschema;
-
-import java.util.Calendar;
-import javax.xml.bind.annotation.adapters.XmlAdapter;
-
-public class Adapter1
- extends XmlAdapter
-{
-
-
- public Calendar unmarshal(String value) {
- return (javax.xml.bind.DatatypeConverter.parseDateTime(value));
- }
-
- public String marshal(Calendar value) {
- if (value == null) {
- return null;
- }
- return (javax.xml.bind.DatatypeConverter.printDateTime(value));
- }
-
-}
+
+package org.w3._2001.xmlschema;
+
+import java.util.Calendar;
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+
+public class Adapter1
+ extends XmlAdapter
+{
+
+
+ public Calendar unmarshal(String value) {
+ return (javax.xml.bind.DatatypeConverter.parseDateTime(value));
+ }
+
+ public String marshal(Calendar value) {
+ if (value == null) {
+ return null;
+ }
+ return (javax.xml.bind.DatatypeConverter.printDateTime(value));
+ }
+
+}
diff --git a/jta/pom.xml b/jta/pom.xml
index e9f9364646..906d28a7ea 100644
--- a/jta/pom.xml
+++ b/jta/pom.xml
@@ -16,6 +16,18 @@
../parent-boot-2
+
+
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
+
+
+
org.springframework.boot
@@ -37,6 +49,7 @@
2.4.7
+ 2.17.1
\ No newline at end of file
diff --git a/ksqldb/pom.xml b/ksqldb/pom.xml
index ee4906090f..e55398d635 100644
--- a/ksqldb/pom.xml
+++ b/ksqldb/pom.xml
@@ -11,7 +11,6 @@
com.baeldungparent-modules1.0.0-SNAPSHOT
- ../pom.xml
diff --git a/libraries-concurrency/pom.xml b/libraries-concurrency/pom.xml
index d8f48a1959..eb581ce3a0 100644
--- a/libraries-concurrency/pom.xml
+++ b/libraries-concurrency/pom.xml
@@ -1,7 +1,7 @@
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0libraries-concurrencylibraries-concurrency
@@ -78,5 +78,5 @@
0.8.0
-
+
\ No newline at end of file
diff --git a/libraries-testing/README.md b/libraries-testing/README.md
index 5498c73094..880c3dd2e6 100644
--- a/libraries-testing/README.md
+++ b/libraries-testing/README.md
@@ -8,7 +8,7 @@ This module contains articles about test libraries.
- [Introduction to JSONassert](https://www.baeldung.com/jsonassert)
- [Serenity BDD and Screenplay](https://www.baeldung.com/serenity-screenplay)
- [Serenity BDD with Spring and JBehave](https://www.baeldung.com/serenity-spring-jbehave)
-- [Introduction to Awaitility](https://www.baeldung.com/awaitlity-testing)
+- [Introduction to Awaitility](https://www.baeldung.com/awaitility-testing)
- [Introduction to Hoverfly in Java](https://www.baeldung.com/hoverfly)
- [Testing with Hamcrest](https://www.baeldung.com/java-junit-hamcrest-guide)
- [Introduction To DBUnit](https://www.baeldung.com/java-dbunit)
diff --git a/logging-modules/log-mdc/pom.xml b/logging-modules/log-mdc/pom.xml
index a21b9a8fb7..c329a9dbdb 100644
--- a/logging-modules/log-mdc/pom.xml
+++ b/logging-modules/log-mdc/pom.xml
@@ -94,7 +94,7 @@
- 2.7
+ 2.17.13.3.63.3.0.Final
diff --git a/logging-modules/log4j/pom.xml b/logging-modules/log4j/pom.xml
index d3c7f8287e..ac7c8620cd 100644
--- a/logging-modules/log4j/pom.xml
+++ b/logging-modules/log4j/pom.xml
@@ -46,8 +46,8 @@
1.2.17
- 2.7
- 2.7
+ 2.17.1
+ 2.17.13.3.6
diff --git a/logging-modules/log4j2/README.md b/logging-modules/log4j2/README.md
index 06f218f469..f53df508bf 100644
--- a/logging-modules/log4j2/README.md
+++ b/logging-modules/log4j2/README.md
@@ -7,3 +7,4 @@
- [Get Log Output in JSON](http://www.baeldung.com/java-log-json-output)
- [System.out.println vs Loggers](https://www.baeldung.com/java-system-out-println-vs-loggers)
- [Log4j 2 Plugins](https://www.baeldung.com/log4j2-plugins)
+- [Printing Thread Info in Log File Using Log4j2](https://www.baeldung.com/log4j2-print-thread-info)
diff --git a/logging-modules/log4j2/pom.xml b/logging-modules/log4j2/pom.xml
index 0b6fa9b902..c3a3f4e0f9 100644
--- a/logging-modules/log4j2/pom.xml
+++ b/logging-modules/log4j2/pom.xml
@@ -111,7 +111,7 @@
2.1.1
- 2.11.0
+ 2.17.1yyyyMMddHHmmss
diff --git a/logging-modules/log4j2/src/main/java/com/baeldung/logging/log4j2threadinfo/Log4j2ThreadInfo.java b/logging-modules/log4j2/src/main/java/com/baeldung/logging/log4j2threadinfo/Log4j2ThreadInfo.java
new file mode 100644
index 0000000000..2c9b8870e2
--- /dev/null
+++ b/logging-modules/log4j2/src/main/java/com/baeldung/logging/log4j2threadinfo/Log4j2ThreadInfo.java
@@ -0,0 +1,18 @@
+package com.baeldung.logging.log4j2threadinfo;
+
+import java.util.stream.IntStream;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class Log4j2ThreadInfo {
+ private static final Logger logger = LogManager.getLogger(Log4j2ThreadInfo.class);
+
+ public static void main(String[] args) {
+ IntStream.range(0, 5).forEach(i -> {
+ Runnable runnable = () -> logger.info("Logging info");
+ Thread thread = new Thread(runnable);
+ thread.start();
+ });
+ }
+}
diff --git a/logging-modules/log4j2/src/main/resources/log4j2.xml b/logging-modules/log4j2/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000..6c0bc75a94
--- /dev/null
+++ b/logging-modules/log4j2/src/main/resources/log4j2.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} --- thread_id="%tid" thread_name="%tn" thread_priority="%tp" --- [%p] %m%n
+
+
+
+
+
+ ${LOG_PATTERN}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lombok-2/README.md b/lombok-2/README.md
index 25d097a7ea..650dc5ddab 100644
--- a/lombok-2/README.md
+++ b/lombok-2/README.md
@@ -7,4 +7,5 @@ This module contains articles about Project Lombok.
- [Using Lombok’s @Accessors Annotation](https://www.baeldung.com/lombok-accessors)
- [Declaring Val and Var Variables in Lombok](https://www.baeldung.com/java-lombok-val-var)
- [Lombok Using @With Annotations](https://www.baeldung.com/lombok-with-annotations)
+- [Lombok's @ToString Annotation](https://www.baeldung.com/lombok-tostring)
- More articles: [[<-- prev]](../lombok)
diff --git a/lombok-2/pom.xml b/lombok-2/pom.xml
index 3c67e959a4..bde328444e 100644
--- a/lombok-2/pom.xml
+++ b/lombok-2/pom.xml
@@ -22,4 +22,4 @@
-
+
\ No newline at end of file
diff --git a/lombok-2/src/main/java/com/baeldung/lombok/tostring/Account.java b/lombok-2/src/main/java/com/baeldung/lombok/tostring/Account.java
new file mode 100644
index 0000000000..641ad1e1a3
--- /dev/null
+++ b/lombok-2/src/main/java/com/baeldung/lombok/tostring/Account.java
@@ -0,0 +1,56 @@
+package com.baeldung.lombok.tostring;
+
+import lombok.ToString;
+
+@ToString
+public class Account {
+
+ private String name;
+
+ // render this field before any others (the highest ranked)
+ @ToString.Include(rank = 1)
+ private String id;
+
+ @ToString.Exclude
+ private String accountNumber;
+
+ // automatically excluded
+ private String $ignored;
+
+ @ToString.Include
+ String description() {
+ return "Account description";
+ }
+
+ public String getAccountNumber() {
+ return accountNumber;
+ }
+
+ public void setAccountNumber(String accountNumber) {
+ this.accountNumber = accountNumber;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String get$ignored() {
+ return $ignored;
+ }
+
+ public void set$ignored(String value) {
+ this.$ignored = value;
+ }
+}
diff --git a/lombok-2/src/main/java/com/baeldung/lombok/tostring/AccountType.java b/lombok-2/src/main/java/com/baeldung/lombok/tostring/AccountType.java
new file mode 100644
index 0000000000..7f99b5ca35
--- /dev/null
+++ b/lombok-2/src/main/java/com/baeldung/lombok/tostring/AccountType.java
@@ -0,0 +1,9 @@
+package com.baeldung.lombok.tostring;
+
+import lombok.ToString;
+
+@ToString
+public enum AccountType {
+ CHECKING,
+ SAVING
+}
diff --git a/lombok-2/src/main/java/com/baeldung/lombok/tostring/RewardAccount.java b/lombok-2/src/main/java/com/baeldung/lombok/tostring/RewardAccount.java
new file mode 100644
index 0000000000..9ac9c6afa6
--- /dev/null
+++ b/lombok-2/src/main/java/com/baeldung/lombok/tostring/RewardAccount.java
@@ -0,0 +1,27 @@
+package com.baeldung.lombok.tostring;
+
+import lombok.ToString;
+
+@ToString
+public class RewardAccount extends Account {
+
+ private String rewardAccountId;
+
+ private Object[] relatedAccounts;
+
+ public String getRewardAccountId() {
+ return rewardAccountId;
+ }
+
+ public void setRewardAccountId(String rewardAccountId) {
+ this.rewardAccountId = rewardAccountId;
+ }
+
+ public Object[] getRelatedAccounts() {
+ return relatedAccounts;
+ }
+
+ public void setRelatedAccounts(Object[] relatedAccounts) {
+ this.relatedAccounts = relatedAccounts;
+ }
+}
diff --git a/lombok-2/src/main/java/com/baeldung/lombok/tostring/SavingAccount.java b/lombok-2/src/main/java/com/baeldung/lombok/tostring/SavingAccount.java
new file mode 100644
index 0000000000..dfba31cf27
--- /dev/null
+++ b/lombok-2/src/main/java/com/baeldung/lombok/tostring/SavingAccount.java
@@ -0,0 +1,17 @@
+package com.baeldung.lombok.tostring;
+
+import lombok.ToString;
+
+@ToString(callSuper = true)
+public class SavingAccount extends Account {
+
+ private String savingAccountId;
+
+ public String getSavingAccountId() {
+ return savingAccountId;
+ }
+
+ public void setSavingAccountId(String savingAccountId) {
+ this.savingAccountId = savingAccountId;
+ }
+}
diff --git a/lombok-2/src/test/java/com/baeldung/lombok/tostring/ToStringUnitTest.java b/lombok-2/src/test/java/com/baeldung/lombok/tostring/ToStringUnitTest.java
new file mode 100644
index 0000000000..cb56ad912d
--- /dev/null
+++ b/lombok-2/src/test/java/com/baeldung/lombok/tostring/ToStringUnitTest.java
@@ -0,0 +1,66 @@
+package com.baeldung.lombok.tostring;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ToStringUnitTest {
+
+ @Test
+ void whenPrintObject_thenOutputIsCorrect() {
+ Account account = new Account();
+ account.setId("12345");
+ account.setName("An account");
+ account.setAccountNumber("11111"); // should not be present in output
+ account.set$ignored("ignored value"); // should not be present in output
+
+ assertThat(account.toString())
+ .isEqualTo("Account(id=12345, name=An account, description=Account description)");
+ }
+
+ @Test
+ void whenPrintSubclassWithSuper_thenOutputIsCorrect() {
+ SavingAccount savingAccount = new SavingAccount();
+ savingAccount.setSavingAccountId("5678");
+ savingAccount.setId("12345");
+ savingAccount.setName("An account");
+
+ assertThat(savingAccount.toString())
+ .isEqualTo("SavingAccount(super=Account(id=12345, name=An account, description=Account description), savingAccountId=5678)");
+ }
+
+@Test
+void whenPrintArrays_thenOutputIsCorrect() {
+ RewardAccount account = new RewardAccount();
+ account.setRewardAccountId("12345");
+
+ // circular ref, just for demonstration
+ Object[] relatedAccounts = new Object[2];
+ relatedAccounts[0] = "54321";
+ relatedAccounts[1] = relatedAccounts;
+
+ account.setRelatedAccounts(relatedAccounts);
+
+ assertThat(account.toString())
+ .isEqualTo("RewardAccount(rewardAccountId=12345, relatedAccounts=[54321, [...]])");
+}
+
+ @Test
+ void whenPrintSubclassWithoutSuper_thenOutputIsCorrect() {
+ RewardAccount rewardAccount = new RewardAccount();
+ rewardAccount.setRewardAccountId("12345");
+
+ assertThat(rewardAccount.toString())
+ .isEqualTo("RewardAccount(rewardAccountId=12345, relatedAccounts=null)");
+ }
+
+ @Test
+ void whenPrintEnum_thenOutputIsCorrect() {
+ assertThat(AccountType.CHECKING.toString())
+ .isEqualTo("AccountType.CHECKING");
+
+ assertThat(AccountType.SAVING.toString())
+ .isEqualTo("AccountType.SAVING");
+ }
+
+}
diff --git a/lombok/pom.xml b/lombok/pom.xml
index d4f89ab4f2..24a04783d1 100644
--- a/lombok/pom.xml
+++ b/lombok/pom.xml
@@ -62,18 +62,9 @@
false
-
+
diff --git a/maven-modules/maven-classifier/README.md b/maven-modules/maven-classifier/README.md
new file mode 100644
index 0000000000..ab8a7f914f
--- /dev/null
+++ b/maven-modules/maven-classifier/README.md
@@ -0,0 +1,3 @@
+### Relevant Articles:
+
+- [A Guide to Maven Artifact Classifiers](https://www.baeldung.com/maven-artifact-classifiers)
diff --git a/maven-modules/maven-classifier/maven-classifier-example-consumer/pom.xml b/maven-modules/maven-classifier/maven-classifier-example-consumer/pom.xml
new file mode 100644
index 0000000000..b280f21b4b
--- /dev/null
+++ b/maven-modules/maven-classifier/maven-classifier-example-consumer/pom.xml
@@ -0,0 +1,53 @@
+
+
+ 4.0.0
+ maven-classifier-example-consumer
+
+
+ maven-classifier
+ com.baeldung
+ 0.0.1-SNAPSHOT
+
+
+
+
+ com.baeldung
+ maven-classifier-example-provider
+ 0.0.1-SNAPSHOT
+
+
+ com.baeldung
+ maven-classifier-example-provider
+ 0.0.1-SNAPSHOT
+ arbitrary
+
+
+
+
+
+
+
+
+
+ com.baeldung
+ maven-classifier-example-provider
+ 0.0.1-SNAPSHOT
+ sources
+
+
+ com.baeldung
+ maven-classifier-example-provider
+ 0.0.1-SNAPSHOT
+ tests
+
+
+
+
+ 8
+ 8
+
+
+
\ No newline at end of file
diff --git a/maven-modules/maven-classifier/maven-classifier-example-consumer/src/main/java/com/baeldung/maven/classifier/consumer/FuelStation.java b/maven-modules/maven-classifier/maven-classifier-example-consumer/src/main/java/com/baeldung/maven/classifier/consumer/FuelStation.java
new file mode 100644
index 0000000000..cf5dc03457
--- /dev/null
+++ b/maven-modules/maven-classifier/maven-classifier-example-consumer/src/main/java/com/baeldung/maven/classifier/consumer/FuelStation.java
@@ -0,0 +1,17 @@
+
+package com.baeldung.maven.classifier.consumer;
+
+import com.baeldung.maven.dependency.classifier.provider.model.Car;
+import com.baeldung.maven.dependency.classifier.provider.model.PowerSource;
+
+public class FuelStation {
+
+ public FuelStation.Zone refill(Car car) {
+ return PowerSource.BATTERY.equals(car.getPowerSource()) ? FuelStation.Zone.BATTERY : FuelStation.Zone.UNKNOWN;
+ }
+
+ public enum Zone {
+ BATTERY,
+ UNKNOWN
+ }
+}
diff --git a/maven-modules/maven-classifier/maven-classifier-example-consumer/src/test/java/com/baeldung/maven/classifier/consumer/FuelStationUnitTest.java b/maven-modules/maven-classifier/maven-classifier-example-consumer/src/test/java/com/baeldung/maven/classifier/consumer/FuelStationUnitTest.java
new file mode 100644
index 0000000000..e04afb8276
--- /dev/null
+++ b/maven-modules/maven-classifier/maven-classifier-example-consumer/src/test/java/com/baeldung/maven/classifier/consumer/FuelStationUnitTest.java
@@ -0,0 +1,21 @@
+package com.baeldung.maven.classifier.consumer;
+
+import com.baeldung.maven.classifier.consumer.FuelStation.Zone;
+import com.baeldung.maven.dependency.classifier.provider.model.Car;
+import com.baeldung.maven.dependency.classifier.provider.stub.CarStub;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class FuelStationUnitTest {
+
+ @Test
+ @DisplayName("Given fuel type battery When request for refill Then Return Battery Zone")
+ public void givenFuelTypeBattery_whenRequestToRefill_thenReturnBatteryZone() {
+ FuelStation fuelStation = new FuelStation();
+ Car electricCar = CarStub.ELECTRIC_CAR;
+
+ assertEquals(Zone.BATTERY, fuelStation.refill(electricCar));
+ }
+}
diff --git a/maven-modules/maven-classifier/maven-classifier-example-provider/pom.xml b/maven-modules/maven-classifier/maven-classifier-example-provider/pom.xml
new file mode 100644
index 0000000000..111996c995
--- /dev/null
+++ b/maven-modules/maven-classifier/maven-classifier-example-provider/pom.xml
@@ -0,0 +1,123 @@
+
+
+ 4.0.0
+ maven-classifier-example-provider
+ 0.0.1-SNAPSHOT
+
+
+ maven-classifier
+ com.baeldung
+ 0.0.1-SNAPSHOT
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.10.0
+
+
+ JDK 8
+ compile
+
+ compile
+
+
+ 8
+ 8
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.2.2
+
+
+ Arbitrary
+
+ jar
+
+
+ arbitrary
+
+
+
+ Test Jar
+
+ test-jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.2.1
+
+
+ attach-sources
+ verify
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.3.2
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+
+
+ 8
+ 8
+
+
+
+
\ No newline at end of file
diff --git a/maven-modules/maven-classifier/maven-classifier-example-provider/src/main/java/com/baeldung/maven/dependency/classifier/provider/factory/CarFactory.java b/maven-modules/maven-classifier/maven-classifier-example-provider/src/main/java/com/baeldung/maven/dependency/classifier/provider/factory/CarFactory.java
new file mode 100644
index 0000000000..285fec9ddc
--- /dev/null
+++ b/maven-modules/maven-classifier/maven-classifier-example-provider/src/main/java/com/baeldung/maven/dependency/classifier/provider/factory/CarFactory.java
@@ -0,0 +1,14 @@
+package com.baeldung.maven.dependency.classifier.provider.factory;
+
+import com.baeldung.maven.dependency.classifier.provider.model.Car;
+import com.baeldung.maven.dependency.classifier.provider.model.Car.Type;
+
+public class CarFactory {
+
+ public static Car manufacture(Type carType) {
+ Car car = new Car();
+ car.setType(carType);
+
+ return car;
+ }
+}
diff --git a/maven-modules/maven-classifier/maven-classifier-example-provider/src/main/java/com/baeldung/maven/dependency/classifier/provider/model/Car.java b/maven-modules/maven-classifier/maven-classifier-example-provider/src/main/java/com/baeldung/maven/dependency/classifier/provider/model/Car.java
new file mode 100644
index 0000000000..93d5091f55
--- /dev/null
+++ b/maven-modules/maven-classifier/maven-classifier-example-provider/src/main/java/com/baeldung/maven/dependency/classifier/provider/model/Car.java
@@ -0,0 +1,26 @@
+package com.baeldung.maven.dependency.classifier.provider.model;
+
+public class Car {
+ private Type type;
+ private PowerSource fuelType;
+
+ public Type getType() {
+ return this.type;
+ }
+
+ public void setType(Type carType) {
+ this.type = carType;
+ }
+
+ public PowerSource getPowerSource() {
+ return this.fuelType;
+ }
+
+ public void setFuelType(PowerSource fuelType) {
+ this.fuelType = fuelType;
+ }
+
+ public enum Type {
+ ELECTRIC
+ }
+}
diff --git a/maven-modules/maven-classifier/maven-classifier-example-provider/src/main/java/com/baeldung/maven/dependency/classifier/provider/model/PowerSource.java b/maven-modules/maven-classifier/maven-classifier-example-provider/src/main/java/com/baeldung/maven/dependency/classifier/provider/model/PowerSource.java
new file mode 100644
index 0000000000..3ac5d98efe
--- /dev/null
+++ b/maven-modules/maven-classifier/maven-classifier-example-provider/src/main/java/com/baeldung/maven/dependency/classifier/provider/model/PowerSource.java
@@ -0,0 +1,5 @@
+package com.baeldung.maven.dependency.classifier.provider.model;
+
+public enum PowerSource {
+ BATTERY
+}
diff --git a/maven-modules/maven-classifier/maven-classifier-example-provider/src/test/java/com/baeldung/maven/dependency/classifier/provider/factory/CarFactoryUnitTest.java b/maven-modules/maven-classifier/maven-classifier-example-provider/src/test/java/com/baeldung/maven/dependency/classifier/provider/factory/CarFactoryUnitTest.java
new file mode 100644
index 0000000000..36f0b36f1c
--- /dev/null
+++ b/maven-modules/maven-classifier/maven-classifier-example-provider/src/test/java/com/baeldung/maven/dependency/classifier/provider/factory/CarFactoryUnitTest.java
@@ -0,0 +1,32 @@
+
+package com.baeldung.maven.dependency.classifier.provider.factory;
+
+import com.baeldung.maven.dependency.classifier.provider.model.Car;
+import com.baeldung.maven.dependency.classifier.provider.model.Car.Type;
+import com.baeldung.maven.dependency.classifier.provider.model.PowerSource;
+import com.baeldung.maven.dependency.classifier.provider.stub.CarStub;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class CarFactoryUnitTest {
+
+ @Test
+ @DisplayName("Given Car type When CarFactory manufacture is called Then create a Car of the given type")
+ public void givenCarType_whenCarFactoryManufactureCalled_thenCreateCarOfGivenType() {
+ Car car = CarFactory.manufacture(Type.ELECTRIC);
+
+ assertNotNull(car, "CarFactory didn't manufacture a car. Car is null");
+ assertEquals(Type.ELECTRIC, car.getType());
+ }
+
+ @Test
+ @DisplayName("Given an electric car When asked for fuel type Then return Battery")
+ public void givenElectricCar_whenAskedForFuelType_thenReturnBattery() {
+ Car car = CarStub.ELECTRIC_CAR;
+
+ assertEquals(PowerSource.BATTERY, car.getPowerSource());
+ }
+}
diff --git a/maven-modules/maven-classifier/maven-classifier-example-provider/src/test/java/com/baeldung/maven/dependency/classifier/provider/stub/CarStub.java b/maven-modules/maven-classifier/maven-classifier-example-provider/src/test/java/com/baeldung/maven/dependency/classifier/provider/stub/CarStub.java
new file mode 100644
index 0000000000..54dbd1166f
--- /dev/null
+++ b/maven-modules/maven-classifier/maven-classifier-example-provider/src/test/java/com/baeldung/maven/dependency/classifier/provider/stub/CarStub.java
@@ -0,0 +1,17 @@
+package com.baeldung.maven.dependency.classifier.provider.stub;
+
+import com.baeldung.maven.dependency.classifier.provider.model.Car;
+import com.baeldung.maven.dependency.classifier.provider.model.PowerSource;
+import com.baeldung.maven.dependency.classifier.provider.model.Car.Type;
+import org.mockito.Mockito;
+
+import static org.mockito.Mockito.when;
+
+public class CarStub {
+ public static Car ELECTRIC_CAR = Mockito.mock(Car.class);
+
+ static {
+ when(ELECTRIC_CAR.getType()).thenReturn(Type.ELECTRIC);
+ when(ELECTRIC_CAR.getPowerSource()).thenReturn(PowerSource.BATTERY);
+ }
+}
diff --git a/maven-modules/maven-classifier/pom.xml b/maven-modules/maven-classifier/pom.xml
new file mode 100644
index 0000000000..ba5f248ff6
--- /dev/null
+++ b/maven-modules/maven-classifier/pom.xml
@@ -0,0 +1,26 @@
+
+
+ 4.0.0
+ maven-classifier
+ pom
+ 0.0.1-SNAPSHOT
+
+
+ maven-classifier-example-consumer
+ maven-classifier-example-provider
+
+
+
+ com.baeldung
+ maven-modules
+ 0.0.1-SNAPSHOT
+
+
+
+ 8
+ 8
+
+
+
\ No newline at end of file
diff --git a/maven-modules/maven-generate-war/pom.xml b/maven-modules/maven-generate-war/pom.xml
index 56256f58ea..51eb54846c 100644
--- a/maven-modules/maven-generate-war/pom.xml
+++ b/maven-modules/maven-generate-war/pom.xml
@@ -6,8 +6,8 @@
com.baeldungmaven-generate-war0.0.1-SNAPSHOT
- warmaven-generate-war
+ warSpring boot project to demonstrate war file generation
@@ -63,6 +63,7 @@
11
+ 2.17.1
\ No newline at end of file
diff --git a/maven-modules/maven-dependency/README.md b/maven-modules/maven-simple/maven-dependency/README.md
similarity index 100%
rename from maven-modules/maven-dependency/README.md
rename to maven-modules/maven-simple/maven-dependency/README.md
diff --git a/maven-modules/maven-dependency/pom.xml b/maven-modules/maven-simple/maven-dependency/pom.xml
similarity index 89%
rename from maven-modules/maven-dependency/pom.xml
rename to maven-modules/maven-simple/maven-dependency/pom.xml
index f17998c327..628c1b62d4 100644
--- a/maven-modules/maven-dependency/pom.xml
+++ b/maven-modules/maven-simple/maven-dependency/pom.xml
@@ -3,14 +3,13 @@
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- com.baeldungmaven-dependency1.0.0-SNAPSHOTpomcom.baeldung
- maven-modules
+ maven-simple0.0.1-SNAPSHOT
@@ -19,7 +18,7 @@
org.apache.commonscommons-lang3
- 3.12.0
+ ${commons-lang3.version}
diff --git a/maven-modules/maven-dependency/src/main/java/com/baeldung/Main.java b/maven-modules/maven-simple/maven-dependency/src/main/java/com/baeldung/Main.java
similarity index 100%
rename from maven-modules/maven-dependency/src/main/java/com/baeldung/Main.java
rename to maven-modules/maven-simple/maven-dependency/src/main/java/com/baeldung/Main.java
diff --git a/maven-modules/maven-profiles/README.md b/maven-modules/maven-simple/maven-profiles/README.md
similarity index 100%
rename from maven-modules/maven-profiles/README.md
rename to maven-modules/maven-simple/maven-profiles/README.md
diff --git a/maven-modules/maven-profiles/pom.xml b/maven-modules/maven-simple/maven-profiles/pom.xml
similarity index 98%
rename from maven-modules/maven-profiles/pom.xml
rename to maven-modules/maven-simple/maven-profiles/pom.xml
index 322dada104..ba915038ad 100644
--- a/maven-modules/maven-profiles/pom.xml
+++ b/maven-modules/maven-simple/maven-profiles/pom.xml
@@ -5,7 +5,7 @@
4.0.0com.baeldungmaven-profiles
- 0.0.1-SNAPSHOT
+ 1.0.0-SNAPSHOTmaven-profiles
diff --git a/maven-modules/maven-simple/parent-project/core/pom.xml b/maven-modules/maven-simple/parent-project/core/pom.xml
new file mode 100644
index 0000000000..a403a59e84
--- /dev/null
+++ b/maven-modules/maven-simple/parent-project/core/pom.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+ org.baeldung
+ core
+ core
+
+
+ parent-project
+ com.baeldung
+ 1.0.0-SNAPSHOT
+
+
+
\ No newline at end of file
diff --git a/maven-modules/maven-simple/parent-project/pom.xml b/maven-modules/maven-simple/parent-project/pom.xml
new file mode 100644
index 0000000000..7193b61ad7
--- /dev/null
+++ b/maven-modules/maven-simple/parent-project/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+ parent-project
+ parent-project
+ pom
+
+
+ com.baeldung
+ maven-simple
+ 1.0.0-SNAPSHOT
+
+
+
+ core
+ service
+ webapp
+
+
+
\ No newline at end of file
diff --git a/maven-modules/maven-simple/parent-project/service/pom.xml b/maven-modules/maven-simple/parent-project/service/pom.xml
new file mode 100644
index 0000000000..ee51b51d1b
--- /dev/null
+++ b/maven-modules/maven-simple/parent-project/service/pom.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+ org.baeldung
+ service
+ service
+
+
+ parent-project
+ com.baeldung
+ 1.0.0-SNAPSHOT
+
+
+
\ No newline at end of file
diff --git a/maven-modules/maven-simple/parent-project/webapp/pom.xml b/maven-modules/maven-simple/parent-project/webapp/pom.xml
new file mode 100644
index 0000000000..d034d4e7d1
--- /dev/null
+++ b/maven-modules/maven-simple/parent-project/webapp/pom.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+ org.baeldung
+ webapp
+ webapp
+
+
+ parent-project
+ com.baeldung
+ 1.0.0-SNAPSHOT
+
+
+
\ No newline at end of file
diff --git a/maven-modules/plugin-management/README.md b/maven-modules/maven-simple/plugin-management/README.md
similarity index 100%
rename from maven-modules/plugin-management/README.md
rename to maven-modules/maven-simple/plugin-management/README.md
diff --git a/maven-modules/plugin-management/pom.xml b/maven-modules/maven-simple/plugin-management/pom.xml
similarity index 89%
rename from maven-modules/plugin-management/pom.xml
rename to maven-modules/maven-simple/plugin-management/pom.xml
index cd75e5dbd0..3c4bd886ac 100644
--- a/maven-modules/plugin-management/pom.xml
+++ b/maven-modules/maven-simple/plugin-management/pom.xml
@@ -4,13 +4,12 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0plugin-management
- 0.0.1-SNAPSHOTpom
- maven-modules
+ maven-simplecom.baeldung
- 0.0.1-SNAPSHOT
+ 1.0.0-SNAPSHOT
@@ -57,8 +56,8 @@
- 3.8.1
- 3.2.0
+ 3.10.0
+ 3.3.0
\ No newline at end of file
diff --git a/maven-modules/plugin-management/submodule-1/pom.xml b/maven-modules/maven-simple/plugin-management/submodule-1/pom.xml
similarity index 92%
rename from maven-modules/plugin-management/submodule-1/pom.xml
rename to maven-modules/maven-simple/plugin-management/submodule-1/pom.xml
index c36e092254..ff08dec9a6 100644
--- a/maven-modules/plugin-management/submodule-1/pom.xml
+++ b/maven-modules/maven-simple/plugin-management/submodule-1/pom.xml
@@ -8,7 +8,7 @@
plugin-managementcom.baeldung
- 0.0.1-SNAPSHOT
+ 1.0.0-SNAPSHOT
diff --git a/maven-modules/plugin-management/submodule-1/src/resources/include.json b/maven-modules/maven-simple/plugin-management/submodule-1/src/resources/include.json
similarity index 100%
rename from maven-modules/plugin-management/submodule-1/src/resources/include.json
rename to maven-modules/maven-simple/plugin-management/submodule-1/src/resources/include.json
diff --git a/maven-modules/plugin-management/submodule-1/src/test/java/com/baeldung/CopiesAdditionalResourcesUnitTest.java b/maven-modules/maven-simple/plugin-management/submodule-1/src/test/java/com/baeldung/CopiesAdditionalResourcesUnitTest.java
similarity index 100%
rename from maven-modules/plugin-management/submodule-1/src/test/java/com/baeldung/CopiesAdditionalResourcesUnitTest.java
rename to maven-modules/maven-simple/plugin-management/submodule-1/src/test/java/com/baeldung/CopiesAdditionalResourcesUnitTest.java
diff --git a/maven-modules/plugin-management/submodule-2/pom.xml b/maven-modules/maven-simple/plugin-management/submodule-2/pom.xml
similarity index 89%
rename from maven-modules/plugin-management/submodule-2/pom.xml
rename to maven-modules/maven-simple/plugin-management/submodule-2/pom.xml
index e50d3cc26d..5db76cebdb 100644
--- a/maven-modules/plugin-management/submodule-2/pom.xml
+++ b/maven-modules/maven-simple/plugin-management/submodule-2/pom.xml
@@ -8,7 +8,7 @@
plugin-managementcom.baeldung
- 0.0.1-SNAPSHOT
+ 1.0.0-SNAPSHOT
\ No newline at end of file
diff --git a/maven-modules/maven-simple/pom.xml b/maven-modules/maven-simple/pom.xml
new file mode 100644
index 0000000000..fe59259758
--- /dev/null
+++ b/maven-modules/maven-simple/pom.xml
@@ -0,0 +1,24 @@
+
+
+ 4.0.0
+ maven-simple
+ maven-simple
+ 1.0.0-SNAPSHOT
+ pom
+
+
+ com.baeldung
+ maven-modules
+ 0.0.1-SNAPSHOT
+
+
+
+ maven-profiles
+ plugin-management
+ maven-dependency
+ parent-project
+
+
+
\ No newline at end of file
diff --git a/maven-modules/pom.xml b/maven-modules/pom.xml
index e09283efb4..0d65e5f9f4 100644
--- a/maven-modules/pom.xml
+++ b/maven-modules/pom.xml
@@ -19,10 +19,10 @@
maven-copy-filesmaven-custom-pluginmaven-exec-plugin
+
maven-integration-testmaven-multi-sourcemaven-plugins
- maven-profilesmaven-propertiesmaven-unused-dependencies
@@ -34,10 +34,10 @@
maven-printing-pluginsmaven-builder-pluginhost-maven-repo-example
- plugin-managementmaven-surefire-pluginmaven-parent-pom-resolution
- maven-dependency
+ maven-simple
+ maven-classifier
diff --git a/metrics/pom.xml b/metrics/pom.xml
index abdfb14dc6..37b10ef484 100644
--- a/metrics/pom.xml
+++ b/metrics/pom.xml
@@ -92,4 +92,4 @@
1.1.0
-
+
\ No newline at end of file
diff --git a/muleesb/pom.xml b/muleesb/pom.xml
index f6cfa7d5d1..d78cebada2 100644
--- a/muleesb/pom.xml
+++ b/muleesb/pom.xml
@@ -108,7 +108,7 @@
org.mule.tools.mavenmule-maven-plugin
- 2.2.1
+ ${mule-maven-plugin.version}standalone${mule.version}
@@ -203,7 +203,7 @@
mulesoft-releasemulesoft release repositorydefault
- https://repository.mulesoft.org/releases/
+ https://repository.mulesoft.org/nexus/content/repositories/public/false
@@ -212,9 +212,10 @@
3.9.0
- 1.2
+ 1.81.3.6
- 1.7
+ 1.10
+ 2.2.1
diff --git a/netty/pom.xml b/netty/pom.xml
index 817b1f2e70..c235ec9f4a 100644
--- a/netty/pom.xml
+++ b/netty/pom.xml
@@ -1,6 +1,7 @@
-
+4.0.0netty0.0.1-SNAPSHOT
@@ -11,20 +12,18 @@
parent-modules1.0.0-SNAPSHOT
-
+
io.nettynetty-all${netty.version}
-
-
+ org.conscryptconscrypt-openjdk-uber${conscrypt-openjdk-uber.version}
-
diff --git a/nginx-forward-proxy/forward b/nginx-forward-proxy/forward
new file mode 100644
index 0000000000..4e8ca8b29f
--- /dev/null
+++ b/nginx-forward-proxy/forward
@@ -0,0 +1,7 @@
+server {
+ listen 8888;
+ location / {
+ resolver 8.8.8.8;
+ proxy_pass http://$http_host$uri$is_args$args;
+ }
+}
\ No newline at end of file
diff --git a/nginx-forward-proxy/package-lock.json b/nginx-forward-proxy/package-lock.json
new file mode 100644
index 0000000000..b70787ec67
--- /dev/null
+++ b/nginx-forward-proxy/package-lock.json
@@ -0,0 +1,339 @@
+{
+ "name": "nginx-forward-proxy",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "asn1": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+ "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
+ "requires": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+ },
+ "aws-sign2": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+ },
+ "aws4": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
+ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
+ },
+ "bcrypt-pbkdf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+ "requires": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "caseless": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+ },
+ "combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+ },
+ "ecc-jsbn": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+ "requires": {
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "extsprintf": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+ },
+ "form-data": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.6",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "har-schema": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+ },
+ "har-validator": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+ "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
+ "requires": {
+ "ajv": "^6.12.3",
+ "har-schema": "^2.0.0"
+ }
+ },
+ "http-signature": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ }
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+ },
+ "jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+ },
+ "json-schema": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+ },
+ "jsprim": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
+ "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
+ "requires": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.4.0",
+ "verror": "1.10.0"
+ }
+ },
+ "mime-db": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
+ "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g=="
+ },
+ "mime-types": {
+ "version": "2.1.34",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
+ "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
+ "requires": {
+ "mime-db": "1.51.0"
+ }
+ },
+ "oauth-sign": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+ },
+ "performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+ },
+ "psl": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+ },
+ "qs": {
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
+ "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA=="
+ },
+ "request": {
+ "version": "2.88.2",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+ "requires": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.3",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.5.0",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.3.2"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "sshpk": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
+ "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
+ "requires": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
+ "tweetnacl": "~0.14.0"
+ }
+ },
+ "tough-cookie": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+ "requires": {
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ }
+ },
+ "tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+ },
+ "uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "uuid": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+ },
+ "verror": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ }
+ }
+ }
+}
diff --git a/nginx-forward-proxy/package.json b/nginx-forward-proxy/package.json
new file mode 100644
index 0000000000..4691542900
--- /dev/null
+++ b/nginx-forward-proxy/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "nginx-forward-proxy",
+ "version": "1.0.0",
+ "description": "Simple Client for Connecting to a Forward Proxy",
+ "main": "proxytest.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "Olsi Seferi",
+ "license": "ISC",
+ "dependencies": {
+ "request": "^2.88.2"
+ }
+}
diff --git a/nginx-forward-proxy/proxytest.js b/nginx-forward-proxy/proxytest.js
new file mode 100644
index 0000000000..dc55ef6620
--- /dev/null
+++ b/nginx-forward-proxy/proxytest.js
@@ -0,0 +1,11 @@
+var request = require('request');
+
+request({
+ 'url':'http://www.google.com/',
+ 'method': "GET",
+ 'proxy':'http://192.168.100.40:8888'
+},function (error, response, body) {
+ if (!error && response.statusCode == 200) {
+ console.log(body);
+ }
+})
diff --git a/parent-boot-2/pom.xml b/parent-boot-2/pom.xml
index 109bf3b64f..b7f33de237 100644
--- a/parent-boot-2/pom.xml
+++ b/parent-boot-2/pom.xml
@@ -88,7 +88,7 @@
3.3.01.0.22.RELEASE
- 2.6.1
+ 2.6.31.9.1
diff --git a/parent-spring-5/pom.xml b/parent-spring-5/pom.xml
index 175e8d58f9..b2d581c9c1 100644
--- a/parent-spring-5/pom.xml
+++ b/parent-spring-5/pom.xml
@@ -24,7 +24,7 @@
- 5.3.13
+ 5.3.165.6.01.5.10.RELEASE
diff --git a/patterns/enterprise-patterns/pom.xml b/patterns/enterprise-patterns/pom.xml
index 10c07d6d05..2228cc9505 100644
--- a/patterns/enterprise-patterns/pom.xml
+++ b/patterns/enterprise-patterns/pom.xml
@@ -16,6 +16,25 @@
wire-tap
+
+
+
+ org.apache.camel.springboot
+ camel-spring-boot-dependencies
+ ${camel.version}
+ pom
+ import
+
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
+
+
+
org.apache.camel.springboot
@@ -39,18 +58,6 @@
-
-
-
- org.apache.camel.springboot
- camel-spring-boot-dependencies
- ${camel.version}
- pom
- import
-
-
-
-
@@ -63,6 +70,7 @@
3.7.42.2.2.RELEASE
+ 2.17.1
\ No newline at end of file
diff --git a/persistence-modules/core-java-persistence-2/README.md b/persistence-modules/core-java-persistence-2/README.md
index 7b215bfef1..f245392cb3 100644
--- a/persistence-modules/core-java-persistence-2/README.md
+++ b/persistence-modules/core-java-persistence-2/README.md
@@ -5,3 +5,4 @@
- [How to Check if a Database Table Exists with JDBC](https://www.baeldung.com/jdbc-check-table-exists)
- [Inserting Null Into an Integer Column Using JDBC](https://www.baeldung.com/jdbc-insert-null-into-integer-column)
- [A Guide to Auto-Commit in JDBC](https://www.baeldung.com/java-jdbc-auto-commit)
+- [JDBC Connection Status](https://www.baeldung.com/jdbc-connection-status)
diff --git a/persistence-modules/core-java-persistence-2/src/main/java/com/baeldung/connectionstatus/ConnectionValidation.java b/persistence-modules/core-java-persistence-2/src/main/java/com/baeldung/connectionstatus/ConnectionValidation.java
new file mode 100644
index 0000000000..b6818d25c2
--- /dev/null
+++ b/persistence-modules/core-java-persistence-2/src/main/java/com/baeldung/connectionstatus/ConnectionValidation.java
@@ -0,0 +1,65 @@
+package com.baeldung.connectionstatus;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+public class ConnectionValidation
+{
+
+ public static Connection getConnection()
+ throws Exception
+ {
+ Class.forName("org.h2.Driver");
+ String url = "jdbc:h2:mem:testdb";
+ return DriverManager.getConnection(url, "user", "password");
+ }
+
+ public static void runIfOpened(Connection connection)
+ throws SQLException
+ {
+ if (connection != null && !connection.isClosed()) {
+ // run sql statements
+ }
+ else {
+ // handle closed connection
+ }
+ }
+
+ public static void runIfValid(Connection connection)
+ throws SQLException
+ {
+ // Try to validate connection with a 5 seconds timeout
+ if (connection.isValid(5)) {
+ // run sql statements
+ }
+ else {
+ // handle invalid connection
+ }
+ }
+
+ public static void runIfConnectionValid(Connection connection)
+ {
+ if (isConnectionValid(connection)) {
+ // run sql statements
+ }
+ else {
+ // handle invalid connection
+ }
+ }
+
+ public static boolean isConnectionValid(Connection connection)
+ {
+ try {
+ if (connection != null && !connection.isClosed()) {
+ // Running a simple validation query
+ connection.prepareStatement("SELECT 1");
+ return true;
+ }
+ }
+ catch (SQLException e) {
+ // log some useful data here
+ }
+ return false;
+ }
+}
diff --git a/persistence-modules/core-java-persistence-2/src/test/java/com/baeldung/connectionstatus/ConnectionValidationUnitTest.java b/persistence-modules/core-java-persistence-2/src/test/java/com/baeldung/connectionstatus/ConnectionValidationUnitTest.java
new file mode 100644
index 0000000000..26356931cc
--- /dev/null
+++ b/persistence-modules/core-java-persistence-2/src/test/java/com/baeldung/connectionstatus/ConnectionValidationUnitTest.java
@@ -0,0 +1,38 @@
+package com.baeldung.connectionstatus;
+
+import org.junit.jupiter.api.Test;
+
+import java.sql.Connection;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ConnectionValidationUnitTest
+{
+ @Test
+ void givenConnectionObject_whenCreated_thenIsNotClosed()
+ throws Exception
+ {
+ Connection connection = ConnectionValidation.getConnection();
+ assertNotNull(connection);
+ assertFalse(connection.isClosed());
+ }
+
+ @Test
+ void givenConnectionObject_whenCreated_thenIsValid()
+ throws Exception
+ {
+ Connection connection = ConnectionValidation.getConnection();
+ assertTrue(connection.isValid(0));
+ }
+
+ @Test
+ void givenConnectionObject_whenValidated_thenIsValid()
+ throws Exception
+ {
+ Connection connection = ConnectionValidation.getConnection();
+ assertTrue(ConnectionValidation.isConnectionValid(connection));
+ }
+
+}
diff --git a/persistence-modules/fauna/.gitignore b/persistence-modules/fauna/.gitignore
new file mode 100644
index 0000000000..c37fa0c4b3
--- /dev/null
+++ b/persistence-modules/fauna/.gitignore
@@ -0,0 +1 @@
+/application.properties
diff --git a/persistence-modules/fauna/pom.xml b/persistence-modules/fauna/pom.xml
new file mode 100644
index 0000000000..67aabb7501
--- /dev/null
+++ b/persistence-modules/fauna/pom.xml
@@ -0,0 +1,56 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.6.2
+
+
+ com.baeldung
+ fauna-blog
+ 0.0.1-SNAPSHOT
+ fauna-blog
+ Blogging Service built with FaunaDB
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ com.faunadb
+ faunadb-java
+ 4.2.0
+ compile
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/FaunaBlogApplication.java b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/FaunaBlogApplication.java
new file mode 100644
index 0000000000..12739342bf
--- /dev/null
+++ b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/FaunaBlogApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.faunablog;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class FaunaBlogApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(FaunaBlogApplication.class, args);
+ }
+
+}
diff --git a/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/FaunaConfiguration.java b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/FaunaConfiguration.java
new file mode 100644
index 0000000000..9964431475
--- /dev/null
+++ b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/FaunaConfiguration.java
@@ -0,0 +1,25 @@
+package com.baeldung.faunablog;
+
+import com.faunadb.client.FaunaClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.net.MalformedURLException;
+
+@Configuration
+class FaunaConfiguration {
+ @Value("https://db.${fauna.region}.fauna.com/")
+ private String faunaUrl;
+
+ @Value("${fauna.secret}")
+ private String faunaSecret;
+
+ @Bean
+ FaunaClient getFaunaClient() throws MalformedURLException {
+ return FaunaClient.builder()
+ .withEndpoint(faunaUrl)
+ .withSecret(faunaSecret)
+ .build();
+ }
+}
diff --git a/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/WebSecurityConfiguration.java b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/WebSecurityConfiguration.java
new file mode 100644
index 0000000000..da99b7578e
--- /dev/null
+++ b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/WebSecurityConfiguration.java
@@ -0,0 +1,35 @@
+package com.baeldung.faunablog;
+
+import com.baeldung.faunablog.users.FaunaUserDetailsService;
+import com.faunadb.client.FaunaClient;
+import org.springframework.beans.factory.annotation.Autowired;
+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.core.userdetails.UserDetailsService;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ private FaunaClient faunaClient;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.csrf().disable();
+ http.authorizeRequests()
+ .antMatchers("/**").permitAll()
+ .and().httpBasic();
+ }
+
+ @Bean
+ @Override
+ public UserDetailsService userDetailsService() {
+ return new FaunaUserDetailsService(faunaClient);
+ }
+}
diff --git a/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/Author.java b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/Author.java
new file mode 100644
index 0000000000..ec4854621d
--- /dev/null
+++ b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/Author.java
@@ -0,0 +1,3 @@
+package com.baeldung.faunablog.posts;
+
+public record Author(String username, String name) {}
diff --git a/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/Post.java b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/Post.java
new file mode 100644
index 0000000000..62b6558a37
--- /dev/null
+++ b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/Post.java
@@ -0,0 +1,5 @@
+package com.baeldung.faunablog.posts;
+
+import java.time.Instant;
+
+public record Post(String id, String title, String content, Author author, Instant created, Long version) {}
diff --git a/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/PostsController.java b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/PostsController.java
new file mode 100644
index 0000000000..e8e6316ea8
--- /dev/null
+++ b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/PostsController.java
@@ -0,0 +1,50 @@
+package com.baeldung.faunablog.posts;
+
+import com.faunadb.client.errors.NotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+@RestController
+@RequestMapping("/posts")
+public class PostsController {
+ @Autowired
+ private PostsService postsService;
+
+ @GetMapping
+ public List listPosts(@RequestParam(value = "author", required = false) String author) throws ExecutionException, InterruptedException {
+ return author == null ? postsService.getAllPosts() : postsService.getAuthorPosts("graham");
+ }
+
+ @GetMapping("/{id}")
+ public Post getPost(@PathVariable("id") String id, @RequestParam(value = "before", required = false) Long before)
+ throws ExecutionException, InterruptedException {
+ return postsService.getPost(id, before);
+ }
+
+ @PostMapping
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ @PreAuthorize("isAuthenticated()")
+ public void createPost(@RequestBody UpdatedPost post) throws ExecutionException, InterruptedException {
+ String name = SecurityContextHolder.getContext().getAuthentication().getName();
+ postsService.createPost(name, post.title(), post.content());
+ }
+
+ @PutMapping("/{id}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ @PreAuthorize("isAuthenticated()")
+ public void updatePost(@PathVariable("id") String id, @RequestBody UpdatedPost post)
+ throws ExecutionException, InterruptedException {
+ postsService.updatePost(id, post.title(), post.content());
+ }
+
+ @ExceptionHandler(NotFoundException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public void postNotFound() {}
+}
diff --git a/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/PostsService.java b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/PostsService.java
new file mode 100644
index 0000000000..5143a24b28
--- /dev/null
+++ b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/PostsService.java
@@ -0,0 +1,124 @@
+package com.baeldung.faunablog.posts;
+
+import com.faunadb.client.FaunaClient;
+import com.faunadb.client.types.Value;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.time.Instant;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+import static com.faunadb.client.query.Language.*;
+
+@Component
+public class PostsService {
+ @Autowired
+ private FaunaClient faunaClient;
+
+ Post getPost(String id, Long before) throws ExecutionException, InterruptedException {
+ var query = Get(Ref(Collection("posts"), id));
+ if (before != null) {
+ query = At(Value(before - 1), query);
+ }
+
+ var postResult= faunaClient.query(
+ Let(
+ "post", query
+ ).in(
+ Obj(
+ "post", Var("post"),
+ "author", Get(Select(Arr(Value("data"), Value("authorRef")), Var("post")))
+ )
+ )).get();
+
+ return parsePost(postResult);
+ }
+
+ List getAllPosts() throws ExecutionException, InterruptedException {
+ var postsResult = faunaClient.query(Map(
+ Paginate(
+ Join(
+ Documents(Collection("posts")),
+ Index("posts_sort_by_created_desc")
+ )
+ ),
+ Lambda(
+ Arr(Value("extra"), Value("ref")),
+ Obj(
+ "post", Get(Var("ref")),
+ "author", Get(Select(Arr(Value("data"), Value("authorRef")), Get(Var("ref"))))
+ )
+ )
+ )).get();
+
+ var posts = postsResult.at("data").asCollectionOf(Value.class).get();
+ return posts.stream().map(this::parsePost).collect(Collectors.toList());
+ }
+
+ List getAuthorPosts(String author) throws ExecutionException, InterruptedException {
+ var postsResult = faunaClient.query(Map(
+ Paginate(
+ Join(
+ Match(Index("posts_by_author"), Select(Value("ref"), Get(Match(Index("users_by_username"), Value(author))))),
+ Index("posts_sort_by_created_desc")
+ )
+ ),
+ Lambda(
+ Arr(Value("extra"), Value("ref")),
+ Obj(
+ "post", Get(Var("ref")),
+ "author", Get(Select(Arr(Value("data"), Value("authorRef")), Get(Var("ref"))))
+ )
+ )
+ )).get();
+
+ var posts = postsResult.at("data").asCollectionOf(Value.class).get();
+ return posts.stream().map(this::parsePost).collect(Collectors.toList());
+ }
+
+ public void createPost(String author, String title, String contents) throws ExecutionException, InterruptedException {
+ faunaClient.query(
+ Create(Collection("posts"),
+ Obj(
+ "data", Obj(
+ "title", Value(title),
+ "contents", Value(contents),
+ "created", Now(),
+ "authorRef", Select(Value("ref"), Get(Match(Index("users_by_username"), Value(author)))))
+ )
+ )
+ ).get();
+ }
+
+ public void updatePost(String id, String title, String contents) throws ExecutionException, InterruptedException {
+ faunaClient.query(
+ Update(Ref(Collection("posts"), id),
+ Obj(
+ "data", Obj(
+ "title", Value(title),
+ "contents", Value(contents))
+ )
+ )
+ ).get();
+ }
+
+ private Post parsePost(Value entry) {
+ var author = entry.at("author");
+ var post = entry.at("post");
+
+ return new Post(
+ post.at("ref").to(Value.RefV.class).get().getId(),
+ post.at("data", "title").to(String.class).get(),
+ post.at("data", "contents").to(String.class).get(),
+ new Author(
+ author.at("data", "username").to(String.class).get(),
+ author.at("data", "name").to(String.class).get()
+ ),
+ post.at("data", "created").to(Instant.class).get(),
+ post.at("ts").to(Long.class).get()
+ );
+ }
+}
diff --git a/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/UpdatedPost.java b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/UpdatedPost.java
new file mode 100644
index 0000000000..9850cd5927
--- /dev/null
+++ b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/posts/UpdatedPost.java
@@ -0,0 +1,3 @@
+package com.baeldung.faunablog.posts;
+
+public record UpdatedPost(String title, String content) {}
diff --git a/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/users/FaunaUserDetailsService.java b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/users/FaunaUserDetailsService.java
new file mode 100644
index 0000000000..2e88aaa477
--- /dev/null
+++ b/persistence-modules/fauna/src/main/java/com/baeldung/faunablog/users/FaunaUserDetailsService.java
@@ -0,0 +1,44 @@
+package com.baeldung.faunablog.users;
+
+import com.faunadb.client.FaunaClient;
+import com.faunadb.client.types.Value;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+import java.util.concurrent.ExecutionException;
+
+import static com.faunadb.client.query.Language.*;
+
+public class FaunaUserDetailsService implements UserDetailsService {
+ private FaunaClient faunaClient;
+
+ public FaunaUserDetailsService(FaunaClient faunaClient) {
+ this.faunaClient = faunaClient;
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ try {
+ Value user = faunaClient.query(Map(
+ Paginate(Match(Index("users_by_username"), Value(username))),
+ Lambda(Value("user"), Get(Var("user")))))
+ .get();
+
+ Value userData = user.at("data").at(0).orNull();
+ if (userData == null) {
+ throw new UsernameNotFoundException("User not found");
+ }
+
+ return User.withDefaultPasswordEncoder()
+ .username(userData.at("data", "username").to(String.class).orNull())
+ .password(userData.at("data", "password").to(String.class).orNull())
+ .roles("USER")
+ .build();
+ } catch (ExecutionException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
+
diff --git a/book b/persistence-modules/fauna/src/main/resources/application.properties
similarity index 100%
rename from book
rename to persistence-modules/fauna/src/main/resources/application.properties
diff --git a/persistence-modules/hibernate-mapping-2/.gitignore b/persistence-modules/hibernate-mapping-2/.gitignore
new file mode 100644
index 0000000000..83c05e60c8
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/.gitignore
@@ -0,0 +1,13 @@
+*.class
+
+#folders#
+/target
+/neoDb*
+/data
+/src/main/webapp/WEB-INF/classes
+*/META-INF/*
+
+# Packaged files #
+*.jar
+*.war
+*.ear
\ No newline at end of file
diff --git a/persistence-modules/hibernate-mapping-2/README.md b/persistence-modules/hibernate-mapping-2/README.md
new file mode 100644
index 0000000000..7a811e17cf
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/README.md
@@ -0,0 +1,7 @@
+## Hibernate Mapping
+
+This module contains articles about Hibernate Mappings.
+
+### Relevant articles
+
+- [Hibernate Many to Many Annotation Tutorial](https://www.baeldung.com/hibernate-many-to-many)
\ No newline at end of file
diff --git a/persistence-modules/hibernate-mapping-2/pom.xml b/persistence-modules/hibernate-mapping-2/pom.xml
new file mode 100644
index 0000000000..8cec8d5d34
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/pom.xml
@@ -0,0 +1,86 @@
+
+
+ 4.0.0
+ hibernate-mapping-2
+ 0.1-SNAPSHOT
+ hibernate-mapping-2
+
+
+ com.baeldung
+ persistence-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+
+ org.springframework
+ spring-context
+ ${org.springframework.version}
+
+
+
+ org.springframework.data
+ spring-data-jpa
+ ${org.springframework.data.version}
+
+
+ org.hibernate
+ hibernate-core
+ ${hibernate.version}
+
+
+ org.apache.tomcat
+ tomcat-dbcp
+ ${tomcat-dbcp.version}
+
+
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+
+ org.springframework
+ spring-test
+ ${org.springframework.version}
+ test
+
+
+ com.h2database
+ h2
+ ${h2.version}
+
+
+ com.sun.xml.bind
+ jaxb-core
+ ${com.sun.xml.version}
+
+
+ javax.xml.bind
+ jaxb-api
+ ${javax.xml.bind.version}
+
+
+ com.sun.xml.bind
+ jaxb-impl
+ ${com.sun.xml.version}
+
+
+
+
+
+ 5.0.2.RELEASE
+ 1.10.6.RELEASE
+
+ 5.2.10.Final
+ 9.0.0.M26
+ 2.3.0.1
+ 2.3.1
+
+
+
\ No newline at end of file
diff --git a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/manytomany/spring/PersistenceConfig.java b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/PersistenceConfig.java
similarity index 93%
rename from persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/manytomany/spring/PersistenceConfig.java
rename to persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/PersistenceConfig.java
index b8e7e1b2fd..0d7b8bdbcf 100644
--- a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/manytomany/spring/PersistenceConfig.java
+++ b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/PersistenceConfig.java
@@ -1,70 +1,72 @@
-package com.baeldung.manytomany.spring;
-
-import java.util.Properties;
-import javax.sql.DataSource;
-import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.PropertySource;
-import org.springframework.core.env.Environment;
-import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
-import org.springframework.orm.hibernate5.HibernateTransactionManager;
-import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.annotation.EnableTransactionManagement;
-
-
-
-@Configuration
-@EnableTransactionManagement
-@PropertySource({ "classpath:persistence-h2.properties" })
-@ComponentScan({ "com.baeldung.hibernate.manytomany" })
-public class PersistenceConfig {
-
- @Autowired
- private Environment env;
-
- @Bean
- public LocalSessionFactoryBean sessionFactory() {
- final LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
- sessionFactory.setDataSource(restDataSource());
- sessionFactory.setPackagesToScan(new String[] { "com.baeldung.hibernate.manytomany" });
- sessionFactory.setHibernateProperties(hibernateProperties());
-
- return sessionFactory;
- }
-
- @Bean
- public DataSource restDataSource() {
- final BasicDataSource dataSource = new BasicDataSource();
- dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
- dataSource.setUrl(env.getProperty("jdbc.url"));
- dataSource.setUsername(env.getProperty("jdbc.user"));
- dataSource.setPassword(env.getProperty("jdbc.pass"));
-
- return dataSource;
- }
-
- @Bean
- public PlatformTransactionManager hibernateTransactionManager() {
- final HibernateTransactionManager transactionManager = new HibernateTransactionManager();
- transactionManager.setSessionFactory(sessionFactory().getObject());
- return transactionManager;
- }
-
- @Bean
- public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
- return new PersistenceExceptionTranslationPostProcessor();
- }
-
- private final Properties hibernateProperties() {
- final Properties hibernateProperties = new Properties();
- hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
- hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
- hibernateProperties.setProperty("hibernate.show_sql", "false");
-
- return hibernateProperties;
- }
-}
+package com.baeldung.manytomany;
+
+import java.util.Properties;
+
+import javax.sql.DataSource;
+
+import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.core.env.Environment;
+import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
+import org.springframework.orm.hibernate5.HibernateTransactionManager;
+import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+
+
+@Configuration
+@EnableTransactionManagement
+@PropertySource({ "classpath:persistence-h2.properties" })
+@ComponentScan({ "com.baeldung.manytomany" })
+public class PersistenceConfig {
+
+ @Autowired
+ private Environment env;
+
+ @Bean
+ public LocalSessionFactoryBean sessionFactory() {
+ final LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
+ sessionFactory.setDataSource(restDataSource());
+ sessionFactory.setPackagesToScan(new String[] { "com.baeldung.manytomany" });
+ sessionFactory.setHibernateProperties(hibernateProperties());
+
+ return sessionFactory;
+ }
+
+ @Bean
+ public DataSource restDataSource() {
+ final BasicDataSource dataSource = new BasicDataSource();
+ dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
+ dataSource.setUrl(env.getProperty("jdbc.url"));
+ dataSource.setUsername(env.getProperty("jdbc.user"));
+ dataSource.setPassword(env.getProperty("jdbc.pass"));
+
+ return dataSource;
+ }
+
+ @Bean
+ public PlatformTransactionManager hibernateTransactionManager() {
+ final HibernateTransactionManager transactionManager = new HibernateTransactionManager();
+ transactionManager.setSessionFactory(sessionFactory().getObject());
+ return transactionManager;
+ }
+
+ @Bean
+ public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
+ return new PersistenceExceptionTranslationPostProcessor();
+ }
+
+ private final Properties hibernateProperties() {
+ final Properties hibernateProperties = new Properties();
+ hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
+ hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
+ hibernateProperties.setProperty("hibernate.show_sql", "false");
+
+ return hibernateProperties;
+ }
+}
diff --git a/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/IEmployeeDao.java b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/IEmployeeDao.java
new file mode 100644
index 0000000000..68bf5d5bad
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/IEmployeeDao.java
@@ -0,0 +1,8 @@
+package com.baeldung.manytomany.dao;
+
+import com.baeldung.manytomany.dao.common.IOperations;
+import com.baeldung.manytomany.model.Employee;
+
+public interface IEmployeeDao extends IOperations{
+
+}
diff --git a/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/IProjectDao.java b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/IProjectDao.java
new file mode 100644
index 0000000000..d2645db44a
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/IProjectDao.java
@@ -0,0 +1,8 @@
+package com.baeldung.manytomany.dao;
+
+import com.baeldung.manytomany.dao.common.IOperations;
+import com.baeldung.manytomany.model.Project;
+
+public interface IProjectDao extends IOperations{
+
+}
diff --git a/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/common/AbstractDao.java b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/common/AbstractDao.java
new file mode 100644
index 0000000000..b37b48e645
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/common/AbstractDao.java
@@ -0,0 +1,14 @@
+package com.baeldung.manytomany.dao.common;
+
+import java.io.Serializable;
+
+import com.google.common.base.Preconditions;
+
+public abstract class AbstractDao implements IOperations {
+
+ protected Class clazz;
+
+ protected final void setClazz(final Class clazzToSet) {
+ clazz = Preconditions.checkNotNull(clazzToSet);
+ }
+}
diff --git a/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/common/AbstractHibernateDao.java b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/common/AbstractHibernateDao.java
new file mode 100644
index 0000000000..9c8a8faa2e
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/common/AbstractHibernateDao.java
@@ -0,0 +1,59 @@
+package com.baeldung.manytomany.dao.common;
+
+import java.io.Serializable;
+import java.util.List;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.google.common.base.Preconditions;
+
+@SuppressWarnings("unchecked")
+public abstract class AbstractHibernateDao extends AbstractDao implements IOperations {
+
+ @Autowired
+ protected SessionFactory sessionFactory;
+
+ // API
+
+ @Override
+ public T findOne(final long id) {
+ return (T) getCurrentSession().get(clazz, id);
+ }
+
+ @Override
+ public List findAll() {
+ return getCurrentSession().createQuery("from " + clazz.getName()).getResultList();
+ }
+
+ @Override
+ public void create(final T entity) {
+ Preconditions.checkNotNull(entity);
+ getCurrentSession().saveOrUpdate(entity);
+ }
+
+ @Override
+ public T update(final T entity) {
+ Preconditions.checkNotNull(entity);
+ return (T) getCurrentSession().merge(entity);
+ }
+
+ @Override
+ public void delete(final T entity) {
+ Preconditions.checkNotNull(entity);
+ getCurrentSession().delete(entity);
+ }
+
+ @Override
+ public void deleteById(final long entityId) {
+ final T entity = findOne(entityId);
+ Preconditions.checkState(entity != null);
+ delete(entity);
+ }
+
+ protected Session getCurrentSession() {
+ return sessionFactory.getCurrentSession();
+ }
+
+}
\ No newline at end of file
diff --git a/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/common/IOperations.java b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/common/IOperations.java
new file mode 100644
index 0000000000..8a85b52fc9
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/common/IOperations.java
@@ -0,0 +1,20 @@
+package com.baeldung.manytomany.dao.common;
+
+import java.io.Serializable;
+import java.util.List;
+
+public interface IOperations {
+
+ T findOne(final long id);
+
+ List findAll();
+
+ void create(final T entity);
+
+ T update(final T entity);
+
+ void delete(final T entity);
+
+ void deleteById(final long entityId);
+
+}
diff --git a/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/impl/EmployeeDao.java b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/impl/EmployeeDao.java
new file mode 100644
index 0000000000..b24013c567
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/impl/EmployeeDao.java
@@ -0,0 +1,17 @@
+package com.baeldung.manytomany.dao.impl;
+
+import org.springframework.stereotype.Repository;
+
+import com.baeldung.manytomany.dao.IEmployeeDao;
+import com.baeldung.manytomany.dao.common.AbstractHibernateDao;
+import com.baeldung.manytomany.model.Employee;
+
+@Repository
+public class EmployeeDao extends AbstractHibernateDao implements IEmployeeDao {
+
+ public EmployeeDao() {
+ super();
+
+ setClazz(Employee.class);
+ }
+}
diff --git a/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/impl/ProjectDao.java b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/impl/ProjectDao.java
new file mode 100644
index 0000000000..a70212f519
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/dao/impl/ProjectDao.java
@@ -0,0 +1,18 @@
+package com.baeldung.manytomany.dao.impl;
+
+import org.springframework.stereotype.Repository;
+
+import com.baeldung.manytomany.dao.IProjectDao;
+import com.baeldung.manytomany.dao.common.AbstractHibernateDao;
+import com.baeldung.manytomany.model.Project;
+
+
+@Repository
+public class ProjectDao extends AbstractHibernateDao implements IProjectDao {
+
+ public ProjectDao() {
+ super();
+
+ setClazz(Project.class);
+ }
+}
diff --git a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/hibernate/manytomany/model/Employee.java b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/model/Employee.java
similarity index 93%
rename from persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/hibernate/manytomany/model/Employee.java
rename to persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/model/Employee.java
index 8157945e2c..39671c21bc 100644
--- a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/hibernate/manytomany/model/Employee.java
+++ b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/model/Employee.java
@@ -1,88 +1,88 @@
-package com.baeldung.hibernate.manytomany.model;
-
-import java.io.Serializable;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.persistence.CascadeType;
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
-import javax.persistence.JoinColumn;
-import javax.persistence.JoinTable;
-import javax.persistence.ManyToMany;
-import javax.persistence.Table;
-
-@Entity
-@Table(name = "Employee")
-public class Employee implements Serializable {
- @Id
- @Column(name = "employee_id")
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long employeeId;
-
- @Column(name = "first_name")
- private String firstName;
-
- @Column(name = "last_name")
- private String lastName;
-
- @ManyToMany(cascade = { CascadeType.ALL })
- @JoinTable(
- name = "Employee_Project",
- joinColumns = { @JoinColumn(name = "employee_id") },
- inverseJoinColumns = { @JoinColumn(name = "project_id") }
- )
- Set projects = new HashSet();
-
-
- public Employee() {
- super();
- }
-
- public Employee(String firstName, String lastName) {
- this.firstName = firstName;
- this.lastName = lastName;
- }
-
- public Employee(String firstName, String lastName, Set projects) {
- this.firstName = firstName;
- this.lastName = lastName;
- this.projects = projects;
- }
-
-
- public Long getEmployeeId() {
- return employeeId;
- }
-
- public void setEmployeeId(Long employeeId) {
- this.employeeId = employeeId;
- }
-
- public String getFirstName() {
- return firstName;
- }
-
- public void setFirstName(String firstName) {
- this.firstName = firstName;
- }
-
- public String getLastName() {
- return lastName;
- }
-
- public void setLastName(String lastName) {
- this.lastName = lastName;
- }
-
- public Set getProjects() {
- return projects;
- }
-
- public void setProjects(Set projects) {
- this.projects = projects;
- }
-}
+package com.baeldung.manytomany.model;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "Employee")
+public class Employee implements Serializable {
+ @Id
+ @Column(name = "employee_id")
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long employeeId;
+
+ @Column(name = "first_name")
+ private String firstName;
+
+ @Column(name = "last_name")
+ private String lastName;
+
+ @ManyToMany(cascade = { CascadeType.ALL })
+ @JoinTable(
+ name = "Employee_Project",
+ joinColumns = { @JoinColumn(name = "employee_id") },
+ inverseJoinColumns = { @JoinColumn(name = "project_id") }
+ )
+ Set projects = new HashSet();
+
+
+ public Employee() {
+ super();
+ }
+
+ public Employee(String firstName, String lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public Employee(String firstName, String lastName, Set projects) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.projects = projects;
+ }
+
+
+ public Long getEmployeeId() {
+ return employeeId;
+ }
+
+ public void setEmployeeId(Long employeeId) {
+ this.employeeId = employeeId;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public Set getProjects() {
+ return projects;
+ }
+
+ public void setProjects(Set projects) {
+ this.projects = projects;
+ }
+}
diff --git a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/hibernate/manytomany/model/Project.java b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/model/Project.java
similarity index 91%
rename from persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/hibernate/manytomany/model/Project.java
rename to persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/model/Project.java
index d6c049f33e..b5dc3cb856 100644
--- a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/hibernate/manytomany/model/Project.java
+++ b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/model/Project.java
@@ -1,61 +1,62 @@
-package com.baeldung.hibernate.manytomany.model;
-
-import java.io.Serializable;
-import java.util.HashSet;
-import java.util.Set;
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.GeneratedValue;
-import javax.persistence.Id;
-import javax.persistence.ManyToMany;
-import javax.persistence.Table;
-
-@Entity
-@Table(name = "Project")
-public class Project implements Serializable {
-
- @Id
- @Column(name = "project_id")
- @GeneratedValue
- private Long projectId;
-
- @Column(name = "title")
- private String title;
-
- @ManyToMany(mappedBy = "projects")
- private Set employees = new HashSet();
-
- public Project() {
- super();
- }
-
- public Project(String title) {
- this.title = title;
- }
-
- public Long getProjectId() {
- return projectId;
- }
-
- public void setProjectId(Long projectId) {
- this.projectId = projectId;
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public Set getEmployees() {
- return employees;
- }
-
- public void setEmployees(Set employees) {
- this.employees = employees;
- }
-
-
-}
+package com.baeldung.manytomany.model;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "Project")
+public class Project implements Serializable {
+
+ @Id
+ @Column(name = "project_id")
+ @GeneratedValue
+ private Long projectId;
+
+ @Column(name = "title")
+ private String title;
+
+ @ManyToMany(mappedBy = "projects")
+ private Set employees = new HashSet();
+
+ public Project() {
+ super();
+ }
+
+ public Project(String title) {
+ this.title = title;
+ }
+
+ public Long getProjectId() {
+ return projectId;
+ }
+
+ public void setProjectId(Long projectId) {
+ this.projectId = projectId;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public Set getEmployees() {
+ return employees;
+ }
+
+ public void setEmployees(Set employees) {
+ this.employees = employees;
+ }
+
+
+}
diff --git a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/hibernate/manytomany/util/HibernateUtil.java b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/util/HibernateUtil.java
similarity index 88%
rename from persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/hibernate/manytomany/util/HibernateUtil.java
rename to persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/util/HibernateUtil.java
index 29e8d7515a..d429564564 100644
--- a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/hibernate/manytomany/util/HibernateUtil.java
+++ b/persistence-modules/hibernate-mapping-2/src/main/java/com/baeldung/manytomany/util/HibernateUtil.java
@@ -1,45 +1,46 @@
-package com.baeldung.hibernate.manytomany.util;
-
-import org.hibernate.SessionFactory;
-import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
-import org.hibernate.cfg.Configuration;
-import org.hibernate.service.ServiceRegistry;
-import com.baeldung.hibernate.manytomany.model.Employee;
-import com.baeldung.hibernate.manytomany.model.Project;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class HibernateUtil {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(HibernateUtil.class);
- private static SessionFactory sessionFactory;
-
- private static SessionFactory buildSessionFactory() {
- try {
- // Create the SessionFactory from hibernate-annotation.cfg.xml
- Configuration configuration = new Configuration();
- configuration.addAnnotatedClass(Employee.class);
- configuration.addAnnotatedClass(Project.class);
- configuration.configure("manytomany.cfg.xml");
- LOGGER.debug("Hibernate Annotation Configuration loaded");
-
- ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties())
- .build();
- LOGGER.debug("Hibernate Annotation serviceRegistry created");
-
- SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
-
- return sessionFactory;
- } catch (Throwable ex) {
- LOGGER.error("Initial SessionFactory creation failed.", ex);
- throw new ExceptionInInitializerError(ex);
- }
- }
-
- public static SessionFactory getSessionFactory() {
- if (sessionFactory == null) {
- sessionFactory = buildSessionFactory();
- }
- return sessionFactory;
- }
-}
+package com.baeldung.manytomany.util;
+
+import org.hibernate.SessionFactory;
+import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.service.ServiceRegistry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.baeldung.manytomany.model.Employee;
+import com.baeldung.manytomany.model.Project;
+
+public class HibernateUtil {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(HibernateUtil.class);
+ private static SessionFactory sessionFactory;
+
+ private static SessionFactory buildSessionFactory() {
+ try {
+ // Create the SessionFactory from hibernate-annotation.cfg.xml
+ Configuration configuration = new Configuration();
+ configuration.addAnnotatedClass(Employee.class);
+ configuration.addAnnotatedClass(Project.class);
+ configuration.configure("manytomany.cfg.xml");
+ LOGGER.debug("Hibernate Annotation Configuration loaded");
+
+ ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties())
+ .build();
+ LOGGER.debug("Hibernate Annotation serviceRegistry created");
+
+ SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
+
+ return sessionFactory;
+ } catch (Throwable ex) {
+ LOGGER.error("Initial SessionFactory creation failed.", ex);
+ throw new ExceptionInInitializerError(ex);
+ }
+ }
+
+ public static SessionFactory getSessionFactory() {
+ if (sessionFactory == null) {
+ sessionFactory = buildSessionFactory();
+ }
+ return sessionFactory;
+ }
+}
diff --git a/persistence-modules/hibernate-mapping-2/src/main/resources/logback.xml b/persistence-modules/hibernate-mapping-2/src/main/resources/logback.xml
new file mode 100644
index 0000000000..ec0dc2469a
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/src/main/resources/logback.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ web - %date [%thread] %-5level %logger{36} - %message%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/persistence-modules/hibernate-mapping-2/src/main/resources/persistence-h2.properties b/persistence-modules/hibernate-mapping-2/src/main/resources/persistence-h2.properties
new file mode 100644
index 0000000000..9c8cf6e02b
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/src/main/resources/persistence-h2.properties
@@ -0,0 +1,11 @@
+# jdbc.X
+jdbc.driverClassName=org.h2.Driver
+jdbc.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
+jdbc.eventGeneratedId=sa
+jdbc.user=sa
+jdbc.pass=
+
+# hibernate.X
+hibernate.dialect=org.hibernate.dialect.H2Dialect
+hibernate.show_sql=false
+hibernate.hbm2ddl.auto=create-drop
\ No newline at end of file
diff --git a/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/SpringContextTest.java b/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/SpringContextTest.java
new file mode 100644
index 0000000000..f1a6f675ce
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/SpringContextTest.java
@@ -0,0 +1,18 @@
+package com.baeldung;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+import com.baeldung.manytomany.PersistenceConfig;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = { PersistenceConfig.class }, loader = AnnotationConfigContextLoader.class)
+public class SpringContextTest {
+
+ @Test
+ public void whenSpringContextIsBootstrapped_thenNoExceptions() {
+ }
+}
diff --git a/persistence-modules/spring-hibernate-5/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationJavaConfigMainIntegrationTest.java b/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationJavaConfigMainIntegrationTest.java
similarity index 76%
rename from persistence-modules/spring-hibernate-5/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationJavaConfigMainIntegrationTest.java
rename to persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationJavaConfigMainIntegrationTest.java
index 614de6d3ad..69b791b4d4 100644
--- a/persistence-modules/spring-hibernate-5/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationJavaConfigMainIntegrationTest.java
+++ b/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationJavaConfigMainIntegrationTest.java
@@ -1,49 +1,57 @@
-package com.baeldung.hibernate.manytomany;
-
-import java.util.HashSet;
-import java.util.Set;
-import org.hibernate.Session;
-import org.hibernate.SessionFactory;
-import org.junit.After;
-import org.junit.Before;
-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 org.springframework.test.context.support.AnnotationConfigContextLoader;
-import com.baeldung.hibernate.manytomany.model.Employee;
-import com.baeldung.hibernate.manytomany.model.Project;
-import com.baeldung.manytomany.spring.PersistenceConfig;
-
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration(classes = { PersistenceConfig.class }, loader = AnnotationConfigContextLoader.class)
-public class HibernateManyToManyAnnotationJavaConfigMainIntegrationTest {
-
- @Autowired
- private SessionFactory sessionFactory;
-
- private Session session;
-
- @Before
- public final void before() {
- session = sessionFactory.openSession();
- session.beginTransaction();
- }
-
- @After
- public final void after() {
- session.getTransaction().commit();
- session.close();
- }
-
- @Test
- public final void whenEntitiesAreCreated_thenNoExceptions() {
- Set projects = new HashSet();
- projects.add(new Project("IT Project"));
- projects.add(new Project("Networking Project"));
- session.persist(new Employee("Peter", "Oven", projects));
- session.persist(new Employee("Allan", "Norman", projects));
- }
-
-}
+package com.baeldung.hibernate.manytomany;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.junit.After;
+import org.junit.Before;
+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 org.springframework.test.context.support.AnnotationConfigContextLoader;
+import com.baeldung.manytomany.PersistenceConfig;
+import com.baeldung.manytomany.model.Employee;
+import com.baeldung.manytomany.model.Project;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = { PersistenceConfig.class }, loader = AnnotationConfigContextLoader.class)
+public class HibernateManyToManyAnnotationJavaConfigMainIntegrationTest {
+
+ @Autowired
+ private SessionFactory sessionFactory;
+
+ private Session session;
+
+ @Before
+ public final void before() {
+ session = sessionFactory.openSession();
+ session.beginTransaction();
+ }
+
+ @After
+ public final void after() {
+ session.getTransaction().commit();
+ session.close();
+ }
+
+ @Test
+ public final void whenEntitiesAreCreated_thenNoExceptions() {
+ Set projects = new HashSet();
+ projects.add(new Project("IT Project"));
+ projects.add(new Project("Networking Project"));
+ session.persist(new Employee("Peter", "Oven", projects));
+ session.persist(new Employee("Allan", "Norman", projects));
+
+ Set employees = new HashSet();
+ employees.add(new Employee("Sam", "Curran"));
+ employees.add(new Employee("Tom", "Curran"));
+ Project project = new Project("Database Project");
+ project.setEmployees(employees);
+ session.persist(project);
+ }
+
+}
diff --git a/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationMainIntegrationTest.java b/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationMainIntegrationTest.java
new file mode 100644
index 0000000000..5255cb040f
--- /dev/null
+++ b/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationMainIntegrationTest.java
@@ -0,0 +1,95 @@
+package com.baeldung.hibernate.manytomany;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baeldung.manytomany.model.Employee;
+import com.baeldung.manytomany.model.Project;
+import com.baeldung.manytomany.util.HibernateUtil;
+
+/**
+ * Configured in: manytomany.cfg.xml
+ */
+public class HibernateManyToManyAnnotationMainIntegrationTest {
+ private static SessionFactory sessionFactory;
+
+ private Session session;
+
+ @BeforeClass
+ public static void beforeTests() {
+ sessionFactory = HibernateUtil.getSessionFactory();
+ }
+
+ @Before
+ public void setUp() {
+ session = sessionFactory.openSession();
+ session.beginTransaction();
+ }
+
+ @Test
+ public void givenSession_whenRead_thenReturnsMtoMdata() {
+ prepareData();
+ @SuppressWarnings("unchecked")
+ List employeeList = session.createQuery("FROM Employee").list();
+ @SuppressWarnings("unchecked")
+ List projectList = session.createQuery("FROM Project").list();
+ assertNotNull(employeeList);
+ assertNotNull(projectList);
+ assertEquals(2, employeeList.size());
+ assertEquals(2, projectList.size());
+
+ for(Employee employee : employeeList) {
+ assertNotNull(employee.getProjects());
+ assertEquals(2, employee.getProjects().size());
+ }
+ for(Project project : projectList) {
+ assertNotNull(project.getEmployees());
+ assertEquals(2, project.getEmployees().size());
+ }
+ }
+
+ private void prepareData() {
+ String[] employeeData = { "Peter Oven", "Allan Norman" };
+ String[] projectData = { "IT Project", "Networking Project" };
+ Set projects = new HashSet();
+
+ for (String proj : projectData) {
+ projects.add(new Project(proj));
+ }
+
+ for (String emp : employeeData) {
+ Employee employee = new Employee(emp.split(" ")[0], emp.split(" ")[1]);
+ employee.setProjects(projects);
+
+ for (Project proj : projects) {
+ proj.getEmployees().add(employee);
+ }
+
+ session.persist(employee);
+ }
+ }
+
+ @After
+ public void tearDown() {
+ session.getTransaction().commit();
+ session.close();
+ }
+
+ @AfterClass
+ public static void afterTests() {
+ sessionFactory.close();
+ }
+
+}
diff --git a/persistence-modules/spring-hibernate-5/src/test/resources/manytomany.cfg.xml b/persistence-modules/hibernate-mapping-2/src/test/resources/manytomany.cfg.xml
similarity index 97%
rename from persistence-modules/spring-hibernate-5/src/test/resources/manytomany.cfg.xml
rename to persistence-modules/hibernate-mapping-2/src/test/resources/manytomany.cfg.xml
index 2ca23d57d3..3ddff9a993 100644
--- a/persistence-modules/spring-hibernate-5/src/test/resources/manytomany.cfg.xml
+++ b/persistence-modules/hibernate-mapping-2/src/test/resources/manytomany.cfg.xml
@@ -13,4 +13,4 @@
falsecreate-drop
-
+
\ No newline at end of file
diff --git a/persistence-modules/hibernate-queries/README.md b/persistence-modules/hibernate-queries/README.md
index 61d94e32de..58ca74cd24 100644
--- a/persistence-modules/hibernate-queries/README.md
+++ b/persistence-modules/hibernate-queries/README.md
@@ -7,4 +7,5 @@ This module contains articles about use of Queries in Hibernate.
- [Criteria Queries Using JPA Metamodel](https://www.baeldung.com/hibernate-criteria-queries-metamodel)
- [Get All Data from a Table with Hibernate](https://www.baeldung.com/hibernate-select-all)
- [Hibernate Named Query](https://www.baeldung.com/hibernate-named-query)
-- [Hibernate Query Plan Cache](https://www.baeldung.com/hibernate-query-plan-cache)
\ No newline at end of file
+- [Hibernate Query Plan Cache](https://www.baeldung.com/hibernate-query-plan-cache)
+- [Hibernate’s addScalar() Method](https://www.baeldung.com/hibernate-addscalar)
diff --git a/persistence-modules/hibernate-queries/src/main/java/com/baeldung/hibernate/scalarmethod/HibernateScalarExample.java b/persistence-modules/hibernate-queries/src/main/java/com/baeldung/hibernate/scalarmethod/HibernateScalarExample.java
new file mode 100644
index 0000000000..69aaaae19d
--- /dev/null
+++ b/persistence-modules/hibernate-queries/src/main/java/com/baeldung/hibernate/scalarmethod/HibernateScalarExample.java
@@ -0,0 +1,47 @@
+package com.baeldung.hibernate.scalarmethod;
+
+import java.util.List;
+
+import org.hibernate.Session;
+import org.hibernate.type.StandardBasicTypes;
+
+public class HibernateScalarExample {
+
+ private Session session;
+
+ public HibernateScalarExample(Session session) {
+ this.session = session;
+ }
+
+ public List
+
\ No newline at end of file
diff --git a/persistence-modules/java-mongodb-2/.gitignore b/persistence-modules/java-mongodb-2/.gitignore
new file mode 100644
index 0000000000..79ba317cb5
--- /dev/null
+++ b/persistence-modules/java-mongodb-2/.gitignore
@@ -0,0 +1,5 @@
+.classpath
+.project
+.settings
+target
+build
\ No newline at end of file
diff --git a/persistence-modules/java-mongodb-2/README.md b/persistence-modules/java-mongodb-2/README.md
new file mode 100644
index 0000000000..1b49e11499
--- /dev/null
+++ b/persistence-modules/java-mongodb-2/README.md
@@ -0,0 +1,5 @@
+## MongoDB
+
+This module contains articles about MongoDB in Java.
+
+
diff --git a/persistence-modules/java-mongodb-2/pom.xml b/persistence-modules/java-mongodb-2/pom.xml
new file mode 100644
index 0000000000..ffc8da0b64
--- /dev/null
+++ b/persistence-modules/java-mongodb-2/pom.xml
@@ -0,0 +1,53 @@
+
+
+ 4.0.0
+ java-mongodb-2
+ 1.0-SNAPSHOT
+ java-mongodb-2
+
+
+ com.baeldung
+ persistence-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+ de.flapdoodle.embedmongo
+ de.flapdoodle.embedmongo
+ ${flapdoodle.version}
+ test
+
+
+ org.mongodb
+ mongo-java-driver
+ ${mongo.version}
+
+
+ dev.morphia.morphia
+ core
+ ${morphia.version}
+
+
+ org.testcontainers
+ mongodb
+ 1.16.3
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.16.3
+ test
+
+
+
+
+ 3.12.1
+ 1.11
+ 1.5.3
+
+
+
diff --git a/persistence-modules/java-mongodb-2/src/main/java/com/baeldung/mongo/update/PustSetOperation.java b/persistence-modules/java-mongodb-2/src/main/java/com/baeldung/mongo/update/PustSetOperation.java
new file mode 100644
index 0000000000..bb7eca4f23
--- /dev/null
+++ b/persistence-modules/java-mongodb-2/src/main/java/com/baeldung/mongo/update/PustSetOperation.java
@@ -0,0 +1,54 @@
+package com.baeldung.mongo.update;
+
+import org.bson.Document;
+
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.Updates;
+import com.mongodb.client.result.UpdateResult;
+
+public class PustSetOperation {
+
+ private static MongoClient mongoClient;
+
+ private static String testCollectionName;
+ private static String databaseName;
+
+ public static void setUp() {
+ if (mongoClient == null) {
+ mongoClient = new MongoClient("localhost", 27017);
+ }
+ databaseName = "baeldung";
+ testCollectionName = "marks";
+ }
+
+ public static void pushSetSolution() {
+
+ MongoDatabase database = mongoClient.getDatabase(databaseName);
+ MongoCollection collection = database.getCollection(testCollectionName);
+
+ Document subjectData = new Document().append("subjectId", 126)
+ .append("subjectName", "Java Programming")
+ .append("marks", 70);
+ UpdateResult updateQueryResult = collection.updateOne(Filters.eq("studentId", 1023), Updates.combine(Updates.set("totalMarks", 170), Updates.push("subjectDetails", subjectData)));
+ System.out.println("updateQueryResult:- " + updateQueryResult);
+
+ }
+
+ public static void main(String args[]) {
+
+ //
+ // Connect to cluster (default is localhost:27017)
+ //
+ setUp();
+
+ //
+ // Push document into the array and set a field
+ //
+ pushSetSolution();
+
+ }
+}
+
diff --git a/persistence-modules/java-mongodb-2/src/main/resources/logback.xml b/persistence-modules/java-mongodb-2/src/main/resources/logback.xml
new file mode 100644
index 0000000000..7d900d8ea8
--- /dev/null
+++ b/persistence-modules/java-mongodb-2/src/main/resources/logback.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/persistence-modules/java-mongodb-2/src/test/java/com/baeldung/mongo/update/PustSetOperationLiveTest.java b/persistence-modules/java-mongodb-2/src/test/java/com/baeldung/mongo/update/PustSetOperationLiveTest.java
new file mode 100644
index 0000000000..6279747429
--- /dev/null
+++ b/persistence-modules/java-mongodb-2/src/test/java/com/baeldung/mongo/update/PustSetOperationLiveTest.java
@@ -0,0 +1,53 @@
+package com.baeldung.update;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import org.bson.Document;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.Updates;
+import com.mongodb.client.result.UpdateResult;
+
+public class PustSetOperationLiveTest {
+
+ private static MongoClient mongoClient;
+ private static MongoDatabase db;
+ private static MongoCollection collection;
+
+ @BeforeClass
+ public static void setup() {
+ if (mongoClient == null) {
+ mongoClient = new MongoClient("localhost", 27017);
+ db = mongoClient.getDatabase("baeldung");
+ collection = db.getCollection("marks");
+
+ collection.insertOne(Document.parse("{\n" + " \"studentId\": 1023,\n" + " \"studentName\":\"James Broad\",\n" + " \"joiningYear\":\"2018\",\n" + " \"totalMarks\":100,\n" + " \"subjectDetails\":[\n"
+ + " {\n" + " \"subjectId\":123,\n" + " \"subjectName\":\"Operating Systems Concepts\",\n" + " \"marks\":4,\n" + " },\n" + " {\n"
+ + " \"subjectId\":124,\n" + " \"subjectName\":\"Numerical Analysis\",\n" + " \"marks\":60\n" + " }\n" + " ]\n" + " }"));
+
+ }
+ }
+
+ @Test
+ public void givenMarksCollection_whenPushSetOperation_thenCheckingForDocument() {
+
+ Document subjectData = new Document().append("subjectId", 126)
+ .append("subjectName", "Java Programming")
+ .append("marks", 70);
+ UpdateResult updateQueryResult = collection.updateOne(Filters.eq("studentId", 1023), Updates.combine(Updates.set("totalMarks", 170), Updates.push("subjectDetails", subjectData)));
+
+ Document studentDetail = collection.find(Filters.eq("studentId", 1023))
+ .first();
+ assertNotNull(studentDetail);
+ assertFalse(studentDetail.isEmpty());
+
+ }
+
+}
+
diff --git a/persistence-modules/java-mongodb/README.md b/persistence-modules/java-mongodb/README.md
index 34acd60c57..2b7fcd3de0 100644
--- a/persistence-modules/java-mongodb/README.md
+++ b/persistence-modules/java-mongodb/README.md
@@ -12,3 +12,5 @@ This module contains articles about MongoDB in Java.
- [MongoDB Aggregations Using Java](https://www.baeldung.com/java-mongodb-aggregations)
- [BSON to JSON Document Conversion in Java](https://www.baeldung.com/java-convert-bson-to-json)
- [How to Check Field Existence in MongoDB?](https://www.baeldung.com/mongodb-check-field-exists)
+- [Get Last Inserted Document ID in MongoDB With Java Driver](https://www.baeldung.com/java-mongodb-last-inserted-id)
+- [Update Multiple Fields in a MongoDB Document](https://www.baeldung.com/mongodb-update-multiple-fields)
diff --git a/persistence-modules/java-mongodb/pom.xml b/persistence-modules/java-mongodb/pom.xml
index 03229e72bd..88f0d18a5b 100644
--- a/persistence-modules/java-mongodb/pom.xml
+++ b/persistence-modules/java-mongodb/pom.xml
@@ -30,6 +30,18 @@
core${morphia.version}
+
+ org.testcontainers
+ mongodb
+ 1.16.3
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.16.3
+ test
+
diff --git a/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/CollectionExistence.java b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/CollectionExistence.java
new file mode 100644
index 0000000000..074913af4e
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/CollectionExistence.java
@@ -0,0 +1,100 @@
+package com.baeldung.mongo;
+
+import java.util.ArrayList;
+
+import org.bson.Document;
+
+import com.mongodb.DB;
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+public class CollectionExistence {
+
+ private static MongoClient mongoClient;
+
+ private static String testCollectionName;
+ private static String databaseName;
+
+ public static void setUp() {
+ if (mongoClient == null) {
+ mongoClient = new MongoClient("localhost", 27017);
+ }
+ databaseName = "baeldung";
+ testCollectionName = "student";
+ }
+
+ public static void collectionExistsSolution() {
+
+ DB db = mongoClient.getDB(databaseName);
+
+ System.out.println("collectionName " + testCollectionName + db.collectionExists(testCollectionName));
+
+ }
+
+ public static void createCollectionSolution() {
+
+ MongoDatabase database = mongoClient.getDatabase(databaseName);
+
+ try {
+ database.createCollection(testCollectionName);
+
+ } catch (Exception exception) {
+ System.err.println("Collection already Exists");
+ }
+
+ }
+
+ public static void listCollectionNamesSolution() {
+
+ MongoDatabase database = mongoClient.getDatabase(databaseName);
+ boolean collectionExists = database.listCollectionNames()
+ .into(new ArrayList())
+ .contains(testCollectionName);
+
+ System.out.println("collectionExists:- " + collectionExists);
+
+ }
+
+ public static void countSolution() {
+
+ MongoDatabase database = mongoClient.getDatabase(databaseName);
+
+ MongoCollection collection = database.getCollection(testCollectionName);
+
+ System.out.println(collection.count());
+
+ }
+
+ public static void main(String args[]) {
+
+ //
+ // Connect to cluster (default is localhost:27017)
+ //
+ setUp();
+
+ //
+ // Check the db existence using DB class's method
+ //
+ collectionExistsSolution();
+
+ //
+ // Check the db existence using the createCollection method of MongoDatabase class
+ //
+ createCollectionSolution();
+
+ //
+ // Check the db existence using the listCollectionNames method of MongoDatabase class
+ //
+ listCollectionNamesSolution();
+
+ //
+ // Check the db existence using the count method of MongoDatabase class
+ //
+ countSolution();
+
+ }
+
+}
+
+
diff --git a/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/PushOperations.java b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/PushOperations.java
new file mode 100644
index 0000000000..fa1f9ddc96
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/PushOperations.java
@@ -0,0 +1,99 @@
+package com.baeldung.mongo;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.bson.Document;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBObject;
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.Updates;
+import com.mongodb.client.result.UpdateResult;
+
+public class PushOperations {
+
+ private static MongoClient mongoClient;
+ private static String testCollectionName;
+ private static String databaseName;
+
+ public static void setUp() {
+ if (mongoClient == null) {
+ mongoClient = new MongoClient("localhost", 27017);
+ }
+
+ databaseName = "baeldung";
+ testCollectionName = "orders";
+
+ }
+
+ public static void pushOperationUsingDBObject() {
+
+ MongoDatabase database = mongoClient.getDatabase(databaseName);
+ MongoCollection collection = database.getCollection(testCollectionName);
+ DBObject listItem = new BasicDBObject("items", new BasicDBObject("itemName", "PIZZA MANIA").append("quantity", 1)
+ .append("price", 800));
+ BasicDBObject searchFilter = new BasicDBObject("customerId", 1023);
+ BasicDBObject updateQuery = new BasicDBObject();
+ updateQuery.append("$push", listItem);
+ UpdateResult updateResult = collection.updateOne(searchFilter, updateQuery);
+
+ System.out.println("updateResult:- " + updateResult);
+ }
+
+ public static void pushOperationUsingDocument() {
+
+ MongoDatabase database = mongoClient.getDatabase(databaseName);
+ MongoCollection collection = database.getCollection(testCollectionName);
+
+ Document item = new Document().append("itemName", "PIZZA MANIA")
+ .append("quantity", 1)
+ .append("price", 800);
+ UpdateResult updateResult = collection.updateOne(Filters.eq("customerId", 1023), Updates.push("items", item));
+
+ System.out.println("updateResult:- " + updateResult);
+ }
+
+ public static void addToSetOperation() {
+
+ MongoDatabase database = mongoClient.getDatabase(databaseName);
+ MongoCollection collection = database.getCollection(testCollectionName);
+
+ Document item = new Document().append("itemName", "PIZZA MANIA")
+ .append("quantity", 1)
+ .append("price", 800);
+ UpdateResult updateResult = collection.updateOne(Filters.eq("customerId", 1023), Updates.addToSet("items", item));
+ System.out.println("updateResult:- " + updateResult);
+ }
+
+ public static void main(String args[]) {
+
+ //
+ // Connect to cluster (default is localhost:27017)
+ //
+ setUp();
+
+ //
+ // Push document into the array using DBObject
+ //
+
+ pushOperationUsingDBObject();
+
+ //
+ // Push document into the array using Document.
+ //
+
+ pushOperationUsingDocument();
+
+ //
+ // Push document into the array using addToSet operator.
+ //
+ addToSetOperation();
+
+ }
+
+}
+
diff --git a/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/objectid/RetrieveIdExample.java b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/objectid/RetrieveIdExample.java
new file mode 100644
index 0000000000..74279bbfcd
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/objectid/RetrieveIdExample.java
@@ -0,0 +1,57 @@
+package com.baeldung.mongo.objectid;
+
+import java.util.Date;
+
+import org.bson.Document;
+import org.bson.types.ObjectId;
+
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+public class RetrieveIdExample {
+
+ public static void main(String[] args) {
+
+ try ( MongoClient mongoClient = new MongoClient("localhost", 27017) ) {
+
+ MongoDatabase database = mongoClient.getDatabase("myMongoDb");
+ MongoCollection collection = database.getCollection("example");
+
+// Create document with user-generated ID
+ ObjectId generatedId = new ObjectId();
+
+ System.out.println(generatedId.toString());
+
+ Document document = new Document();
+ document.put("_id", generatedId);
+ document.put("name", "Shubham");
+ document.put("company", "Baeldung");
+ collection.insertOne(document);
+
+// Check that the ID of the document is still the one we set
+ System.out.println(document.getObjectId("_id").equals(generatedId));
+
+
+// Create a second document by injecting the ID in the constructor
+
+ ObjectId generatedId2 = ObjectId.get();
+
+ Document document2 = new Document("_id", generatedId2);
+ document2.put("name", "Shubham");
+ document2.put("company", "Baeldung");
+ collection.insertOne(document2);
+
+ Date creationDate = generatedId.getDate();
+ System.out.println(creationDate);
+
+ int timestamp = generatedId.getTimestamp();
+
+ } catch (Exception e) {
+
+ System.out.println(e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/update/MultipleFieldsExample.java b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/update/MultipleFieldsExample.java
new file mode 100644
index 0000000000..ebc56cbfd0
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/update/MultipleFieldsExample.java
@@ -0,0 +1,44 @@
+package com.baeldung.mongo.update;
+
+import org.bson.Document;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.result.UpdateResult;
+
+public class MultipleFieldsExample {
+
+ public static void main(String[] args) {
+
+ //
+ // Connect to cluster (default is localhost:27017)
+ //
+
+ MongoClient mongoClient = new MongoClient("localhost", 27017);
+ MongoDatabase database = mongoClient.getDatabase("baeldung");
+ MongoCollection collection = database.getCollection("employee");
+
+ //
+ // Filter on the basis of employee_id
+ //
+
+ BasicDBObject searchQuery = new BasicDBObject("employee_id", 794875);
+
+ //
+ // Update the fields in Document
+ //
+
+ BasicDBObject updateFields = new BasicDBObject();
+ updateFields.append("department_id", 3);
+ updateFields.append("job", "Sales Manager");
+ BasicDBObject setQuery = new BasicDBObject();
+ setQuery.append("$set", updateFields);
+ UpdateResult updateResult = collection.updateMany(searchQuery, setQuery);
+
+ System.out.println("updateResult:- " + updateResult);
+ System.out.println("updateResult:- " + updateResult.getModifiedCount());
+
+ }
+}
diff --git a/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/update/UpdateFields.java b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/update/UpdateFields.java
new file mode 100644
index 0000000000..a1b051e74c
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/update/UpdateFields.java
@@ -0,0 +1,128 @@
+package com.baeldung.mongo.update;
+
+import org.bson.Document;
+
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.FindOneAndReplaceOptions;
+import com.mongodb.client.model.FindOneAndUpdateOptions;
+import com.mongodb.client.model.ReturnDocument;
+import com.mongodb.client.model.Updates;
+import com.mongodb.client.result.UpdateResult;
+
+public class UpdateFields {
+
+ private static MongoClient mongoClient;
+ private static MongoDatabase database;
+ private static MongoCollection collection;
+
+ public static void updateOne() {
+
+ UpdateResult updateResult = collection.updateOne(Filters.eq("student_name", "Paul Starc"), Updates.set("address", "Hostel 2"));
+
+ System.out.println("updateResult:- " + updateResult);
+ }
+
+ public static void updateMany() {
+
+ UpdateResult updateResult = collection.updateMany(Filters.lt("age", 20), Updates.set("Review", true));
+
+ System.out.println("updateResult:- " + updateResult);
+
+ }
+
+ public static void replaceOne() {
+
+ Document replaceDocument = new Document();
+ replaceDocument.append("student_id", 8764)
+ .append("student_name", "Paul Starc")
+ .append("address", "Hostel 3")
+ .append("age", 18)
+ .append("roll_no", 199406);
+
+ UpdateResult updateResult = collection.replaceOne(Filters.eq("student_id", 8764), replaceDocument);
+
+ System.out.println("updateResult:- " + updateResult);
+
+ }
+
+ public static void findOneAndReplace() {
+
+ Document replaceDocument = new Document();
+ replaceDocument.append("student_id", 8764)
+ .append("student_name", "Paul Starc")
+ .append("address", "Hostel 4")
+ .append("age", 18)
+ .append("roll_no", 199406);
+ Document sort = new Document("roll_no", 1);
+ Document projection = new Document("_id", 0).append("student_id", 1)
+ .append("address", 1);
+ Document resultDocument = collection.findOneAndReplace(Filters.eq("student_id", 8764), replaceDocument, new FindOneAndReplaceOptions().upsert(true)
+ .sort(sort)
+ .projection(projection)
+ .returnDocument(ReturnDocument.AFTER));
+
+ System.out.println("resultDocument:- " + resultDocument);
+
+ }
+
+ public static void findOneAndUpdate() {
+
+ Document sort = new Document("roll_no", 1);
+ Document projection = new Document("_id", 0).append("student_id", 1)
+ .append("address", 1);
+ Document resultDocument = collection.findOneAndUpdate(Filters.eq("student_id", 8764), Updates.inc("roll_no", 5), new FindOneAndUpdateOptions().upsert(true)
+ .sort(sort)
+ .projection(projection)
+ .returnDocument(ReturnDocument.AFTER));
+
+ System.out.println("resultDocument:- " + resultDocument);
+ }
+
+ public static void setup() {
+ if (mongoClient == null) {
+ mongoClient = new MongoClient("localhost", 27017);
+ database = mongoClient.getDatabase("baeldung");
+ collection = database.getCollection("student");
+
+ }
+ }
+
+ public static void main(String[] args) {
+
+ //
+ // Connect to cluster (default is localhost:27017)
+ //
+ setup();
+
+ //
+ // Update a document using updateOne method
+ //
+ updateOne();
+
+ //
+ // Update documents using updateMany method
+ //
+ updateMany();
+
+ //
+ // replace a document using replaceOne method
+ //
+ replaceOne();
+
+ //
+ // replace a document using findOneAndReplace method
+ //
+ findOneAndReplace();
+
+ //
+ // Update a document using findOneAndUpdate method
+ //
+ findOneAndUpdate();
+
+ }
+
+}
+
diff --git a/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/update/UpdateMultipleFields.java b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/update/UpdateMultipleFields.java
new file mode 100644
index 0000000000..96dd086ed7
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/update/UpdateMultipleFields.java
@@ -0,0 +1,35 @@
+package com.baeldung.mongo.update;
+
+import org.bson.Document;
+
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.Updates;
+import com.mongodb.client.result.UpdateResult;
+
+public class UpdateMultipleFields {
+
+ public static void main(String[] args) {
+
+ //
+ // Connect to cluster
+ //
+
+ MongoClient mongoClient = new MongoClient("localhost", 27007);
+ MongoDatabase database = mongoClient.getDatabase("baeldung");
+ MongoCollection collection = database.getCollection("employee");
+
+ //
+ // Update query
+ //
+
+ UpdateResult updateResult = collection.updateMany(Filters.eq("employee_id", 794875), Updates.combine(Updates.set("department_id", 4), Updates.set("job", "Sales Manager")));
+
+ System.out.println("updateResult:- " + updateResult);
+ System.out.println("updateResult:- " + updateResult.getModifiedCount());
+
+ }
+
+}
diff --git a/persistence-modules/java-mongodb/src/test/java/com/baeldung/mongo/CollectionExistenceLiveTest.java b/persistence-modules/java-mongodb/src/test/java/com/baeldung/mongo/CollectionExistenceLiveTest.java
new file mode 100644
index 0000000000..ad839d1219
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/test/java/com/baeldung/mongo/CollectionExistenceLiveTest.java
@@ -0,0 +1,98 @@
+package com.baeldung.mongo;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+
+import org.bson.Document;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.mongodb.DB;
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+public class CollectionExistenceLiveTest {
+
+ private MongoClient mongoClient;
+ private String testCollectionName;
+ private String databaseName;
+
+ @Before
+ public void setup() {
+ if (mongoClient == null) {
+ mongoClient = new MongoClient("localhost", 27017);
+ databaseName = "baeldung";
+ testCollectionName = "student";
+
+ // Create a new collection if it doesn't exists.
+ try {
+ MongoDatabase database = mongoClient.getDatabase(databaseName);
+ database.createCollection(testCollectionName);
+
+ } catch (Exception exception) {
+
+ System.out.println("Collection already Exists");
+ }
+
+ }
+ }
+
+ @Test
+ public void givenCreateCollection_whenCollectionAlreadyExists_thenCheckingForCollectionExistence() {
+
+ MongoDatabase database = mongoClient.getDatabase(databaseName);
+ Boolean collectionStatus = false;
+ Boolean expectedStatus = true;
+
+ try {
+ database.createCollection(testCollectionName);
+
+ } catch (Exception exception) {
+ collectionStatus = true;
+ System.err.println("Collection already Exists");
+ }
+
+ assertEquals(expectedStatus, collectionStatus);
+
+ }
+
+ @Test
+ public void givenCollectionExists_whenCollectionAlreadyExists_thenCheckingForCollectionExistence() {
+
+ DB db = mongoClient.getDB(databaseName);
+ Boolean collectionStatus = db.collectionExists(testCollectionName);
+
+ Boolean expectedStatus = true;
+ assertEquals(expectedStatus, collectionStatus);
+
+ }
+
+ @Test
+ public void givenListCollectionNames_whenCollectionAlreadyExists_thenCheckingForCollectionExistence() {
+
+ MongoDatabase database = mongoClient.getDatabase(databaseName);
+ boolean collectionExists = database.listCollectionNames()
+ .into(new ArrayList())
+ .contains(testCollectionName);
+
+ Boolean expectedStatus = true;
+ assertEquals(expectedStatus, collectionExists);
+
+ }
+
+ @Test
+ public void givenCount_whenCollectionAlreadyExists_thenCheckingForCollectionExistence() {
+
+ MongoDatabase database = mongoClient.getDatabase(databaseName);
+
+ MongoCollection collection = database.getCollection(testCollectionName);
+ Boolean collectionExists = collection.count() > 0 ? true : false;
+
+ Boolean expectedStatus = false;
+ assertEquals(expectedStatus, collectionExists);
+
+ }
+}
+
diff --git a/persistence-modules/java-mongodb/src/test/java/com/baeldung/mongo/PushOperationLiveTest.java b/persistence-modules/java-mongodb/src/test/java/com/baeldung/mongo/PushOperationLiveTest.java
new file mode 100644
index 0000000000..bd8523b301
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/test/java/com/baeldung/mongo/PushOperationLiveTest.java
@@ -0,0 +1,92 @@
+package com.baeldung.mongo;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import org.bson.Document;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBObject;
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.Updates;
+import com.mongodb.client.result.UpdateResult;
+
+public class PushOperationLiveTest {
+
+ private static MongoClient mongoClient;
+ private static MongoDatabase db;
+ private static MongoCollection collection;
+
+ @BeforeClass
+ public static void setup() {
+ if (mongoClient == null) {
+ mongoClient = new MongoClient("localhost", 27017);
+ db = mongoClient.getDatabase("baeldung");
+ collection = db.getCollection("orders");
+
+ collection.insertOne(
+ Document.parse("{\n" + " \"customerId\": 1023,\n" + " \"orderTimestamp\": NumberLong(\"1646460073000\"),\n" + " \"shippingDestination\": \"336, Street No.1 Pawai Mumbai\",\n" + " \"purchaseOrder\": 1000,\n"
+ + " \"contactNumber\":\"9898987676\",\n" + " \"items\": [ \n" + " {\n" + " \"itemName\": \"BERGER\",\n" + " \"quantity\": 1,\n" + " \"price\": 500\n" + " },\n"
+ + " {\n" + " \"itemName\": \"VEG PIZZA\",\n" + " \"quantity\": 1,\n" + " \"price\": 800\n" + " } \n" + " ]\n" + " }"));
+ }
+ }
+
+ @Test
+ public void givenOrderCollection_whenPushOperationUsingDBObject_thenCheckingForDocument() {
+
+ DBObject listItem = new BasicDBObject("items", new BasicDBObject("itemName", "PIZZA MANIA").append("quantity", 1)
+ .append("price", 800));
+ BasicDBObject searchFilter = new BasicDBObject("customerId", 1023);
+ BasicDBObject updateQuery = new BasicDBObject();
+ updateQuery.append("$push", listItem);
+ UpdateResult updateResult = collection.updateOne(searchFilter, updateQuery);
+
+ Document orderDetail = collection.find(Filters.eq("customerId", 1023))
+ .first();
+ assertNotNull(orderDetail);
+ assertFalse(orderDetail.isEmpty());
+
+ }
+
+ @Test
+ public void givenOrderCollection_whenPushOperationUsingDocument_thenCheckingForDocument() {
+
+ Document item = new Document().append("itemName", "PIZZA MANIA")
+ .append("quantity", 1)
+ .append("price", 800);
+ UpdateResult updateResult = collection.updateOne(Filters.eq("customerId", 1023), Updates.push("items", item));
+
+ Document orderDetail = collection.find(Filters.eq("customerId", 1023))
+ .first();
+ assertNotNull(orderDetail);
+ assertFalse(orderDetail.isEmpty());
+
+ }
+
+ @Test
+ public void givenOrderCollection_whenAddToSetOperation_thenCheckingForDocument() {
+
+ Document item = new Document().append("itemName", "PIZZA MANIA")
+ .append("quantity", 1)
+ .append("price", 800);
+ UpdateResult updateResult = collection.updateOne(Filters.eq("customerId", 1023), Updates.addToSet("items", item));
+
+ Document orderDetail = collection.find(Filters.eq("customerId", 1023))
+ .first();
+ assertNotNull(orderDetail);
+ assertFalse(orderDetail.isEmpty());
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ mongoClient.close();
+ }
+
+}
+
diff --git a/persistence-modules/java-mongodb/src/test/java/com/baeldung/ordering/caseinsensitive/CaseInsensitiveOrderingLiveTest.java b/persistence-modules/java-mongodb/src/test/java/com/baeldung/ordering/caseinsensitive/CaseInsensitiveOrderingLiveTest.java
new file mode 100644
index 0000000000..ec2c332018
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/test/java/com/baeldung/ordering/caseinsensitive/CaseInsensitiveOrderingLiveTest.java
@@ -0,0 +1,108 @@
+package com.baeldung.ordering.caseinsensitive;
+
+import com.mongodb.MongoClient;
+import com.mongodb.client.*;
+import com.mongodb.client.model.Collation;
+import com.mongodb.client.model.Projections;
+import com.mongodb.client.model.Sorts;
+import org.bson.Document;
+import org.bson.conversions.Bson;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.MongoDBContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.mongodb.client.model.Aggregates.project;
+import static com.mongodb.client.model.Aggregates.sort;
+import static com.mongodb.client.model.Sorts.ascending;
+import static org.junit.Assert.assertEquals;
+
+@Testcontainers
+class CaseInsensitiveOrderingLiveTest {
+
+ private static MongoCollection userCollections;
+
+ @Container
+ private static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
+
+ @BeforeAll
+ private static void setup() {
+
+ MongoClient mongoClient = new MongoClient(mongoDBContainer.getContainerIpAddress(), mongoDBContainer.getMappedPort(27017));
+ MongoDatabase database = mongoClient.getDatabase("test");
+ userCollections = database.getCollection("users");
+
+ List list = new ArrayList<>();
+ list.add(Document.parse("{'name': 'ben', surname: 'ThisField' }"));
+ list.add(Document.parse("{'name': 'aen', surname: 'Does' }"));
+ list.add(Document.parse("{'name': 'Ben', surname: 'Not' }"));
+ list.add(Document.parse("{'name': 'cen', surname: 'Matter' }"));
+ list.add(Document.parse("{'name': 'Aen', surname: 'Really' }"));
+ list.add(Document.parse("{'name': 'Cen', surname: 'TrustMe' }"));
+
+ userCollections.insertMany(list);
+ }
+
+ @Test
+ void givenMongoCollection_whenUsingFindWithSort_caseIsConsideredByDefault() {
+ FindIterable nameDoc = userCollections.find().sort(ascending("name"));
+ MongoCursor cursor = nameDoc.cursor();
+
+ List expectedNamesOrdering = Arrays.asList("Aen", "Ben", "Cen", "aen", "ben", "cen");
+ List actualNamesOrdering = new ArrayList<>();
+ while (cursor.hasNext()) {
+ Document document = cursor.next();
+ actualNamesOrdering.add(document.get("name").toString());
+ }
+
+ assertEquals(expectedNamesOrdering, actualNamesOrdering);
+ }
+
+ @Test
+ void givenMongoCollection_whenUsingFindWithSortAndCollation_caseIsNotConsidered() {
+ FindIterable nameDoc = userCollections.find().sort(ascending("name"))
+ .collation(Collation.builder().locale("en").build());
+ MongoCursor cursor = nameDoc.cursor();
+ List expectedNamesOrdering = Arrays.asList("aen", "Aen", "ben", "Ben", "cen", "Cen");
+ List actualNamesOrdering = new ArrayList<>();
+ while (cursor.hasNext()) {
+ Document document = cursor.next();
+ actualNamesOrdering.add(document.get("name").toString());
+ }
+
+ assertEquals(expectedNamesOrdering, actualNamesOrdering);
+
+ }
+
+ @Test
+ void givenMongoCollection_whenUsingFindWithSortAndAggregate_caseIsNotConsidered() {
+
+ Bson projectBson = project(
+ Projections.fields(
+ Projections.include("name", "surname"),
+ Projections.computed("lowerName", Projections.computed("$toLower", "$name"))));
+
+ AggregateIterable nameDoc = userCollections.aggregate(
+ Arrays.asList(projectBson,
+ sort(Sorts.ascending("lowerName"))));
+
+ MongoCursor cursor = nameDoc.cursor();
+
+ List expectedNamesOrdering = Arrays.asList("aen", "Aen", "ben", "Ben", "cen", "Cen");
+ List actualNamesOrdering = new ArrayList<>();
+ while (cursor.hasNext()) {
+ Document document = cursor.next();
+ actualNamesOrdering.add(document.get("name").toString());
+ }
+
+ assertEquals(expectedNamesOrdering, actualNamesOrdering);
+ }
+
+
+}
diff --git a/persistence-modules/java-mongodb/src/test/java/com/baeldung/update/UpdateFieldLiveTest.java b/persistence-modules/java-mongodb/src/test/java/com/baeldung/update/UpdateFieldLiveTest.java
new file mode 100644
index 0000000000..47114e1f1a
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/test/java/com/baeldung/update/UpdateFieldLiveTest.java
@@ -0,0 +1,143 @@
+package com.baeldung.update;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import org.bson.Document;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.FindOneAndReplaceOptions;
+import com.mongodb.client.model.FindOneAndUpdateOptions;
+import com.mongodb.client.model.ReturnDocument;
+import com.mongodb.client.model.Updates;
+import com.mongodb.client.result.UpdateResult;
+
+public class UpdateFieldLiveTest {
+
+ private static MongoClient mongoClient;
+ private static MongoDatabase db;
+ private static MongoCollection collection;
+
+ @BeforeClass
+ public static void setup() {
+ if (mongoClient == null) {
+ mongoClient = new MongoClient("localhost", 27017);
+ db = mongoClient.getDatabase("baeldung");
+ collection = db.getCollection("student");
+
+ collection.insertOne(Document.parse("{ \"student_id\": 8764,\"student_name\": \"Paul Starc\",\"address\": \"Hostel 1\",\"age\": 16,\"roll_no\":199406}"));
+ }
+ }
+
+ @Test
+ public void updateOne() {
+
+ UpdateResult updateResult = collection.updateOne(Filters.eq("student_name", "Paul Starc"), Updates.set("address", "Hostel 2"));
+
+ Document studentDetail = collection.find(Filters.eq("student_name", "Paul Starc"))
+ .first();
+ assertNotNull(studentDetail);
+ assertFalse(studentDetail.isEmpty());
+
+ String address = studentDetail.getString("address");
+ String expectedAdderess = "Hostel 2";
+
+ assertEquals(expectedAdderess, address);
+ }
+
+ @Test
+ public void updateMany() {
+
+ UpdateResult updateResult = collection.updateMany(Filters.lt("age", 20), Updates.set("Review", true));
+
+ Document studentDetail = collection.find(Filters.eq("student_name", "Paul Starc"))
+ .first();
+ assertNotNull(studentDetail);
+ assertFalse(studentDetail.isEmpty());
+
+ Boolean review = studentDetail.getBoolean("Review");
+ Boolean expectedAdderess = true;
+
+ assertEquals(expectedAdderess, review);
+
+ }
+
+ @Test
+ public void replaceOne() {
+
+ Document replaceDocument = new Document();
+ replaceDocument.append("student_id", 8764)
+ .append("student_name", "Paul Starc")
+ .append("address", "Hostel 3")
+ .append("age", 18)
+ .append("roll_no", 199406);
+
+ UpdateResult updateResult = collection.replaceOne(Filters.eq("student_id", 8764), replaceDocument);
+
+ Document studentDetail = collection.find(Filters.eq("student_name", "Paul Starc"))
+ .first();
+ assertNotNull(studentDetail);
+ assertFalse(studentDetail.isEmpty());
+
+ Integer age = studentDetail.getInteger("age");
+ Integer expectedAge = 18;
+
+ assertEquals(expectedAge, age);
+
+ }
+
+ @Test
+ public void findOneAndReplace() {
+
+ Document replaceDocument = new Document();
+ replaceDocument.append("student_id", 8764)
+ .append("student_name", "Paul Starc")
+ .append("address", "Hostel 4")
+ .append("age", 18)
+ .append("roll_no", 199406);
+ Document sort = new Document("roll_no", 1);
+ Document projection = new Document("_id", 0).append("student_id", 1)
+ .append("address", 1);
+ Document resultDocument = collection.findOneAndReplace(Filters.eq("student_id", 8764), replaceDocument, new FindOneAndReplaceOptions().upsert(true)
+ .sort(sort)
+ .projection(projection)
+ .returnDocument(ReturnDocument.AFTER));
+
+ Document studentDetail = collection.find(Filters.eq("student_name", "Paul Starc"))
+ .first();
+ assertNotNull(studentDetail);
+ assertFalse(studentDetail.isEmpty());
+
+ Integer age = studentDetail.getInteger("age");
+ Integer expectedAge = 18;
+
+ assertEquals(expectedAge, age);
+
+ }
+
+ @Test
+ public void findOneAndUpdate() {
+
+ Document sort = new Document("roll_no", 1);
+ Document projection = new Document("_id", 0).append("student_id", 1)
+ .append("address", 1);
+ Document resultDocument = collection.findOneAndUpdate(Filters.eq("student_id", 8764), Updates.inc("roll_no", 5), new FindOneAndUpdateOptions().upsert(true)
+ .sort(sort)
+ .projection(projection)
+ .returnDocument(ReturnDocument.AFTER));
+
+ Document studentDetail = collection.find(Filters.eq("student_name", "Paul Starc"))
+ .first();
+ assertNotNull(studentDetail);
+ assertFalse(studentDetail.isEmpty());
+
+ }
+
+}
+
diff --git a/persistence-modules/java-mongodb/src/test/java/com/baeldung/update/UpdateMultipleFieldsLiveTest.java b/persistence-modules/java-mongodb/src/test/java/com/baeldung/update/UpdateMultipleFieldsLiveTest.java
new file mode 100644
index 0000000000..d06de23423
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/test/java/com/baeldung/update/UpdateMultipleFieldsLiveTest.java
@@ -0,0 +1,78 @@
+package com.baeldung.update;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import org.bson.Document;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.Updates;
+
+public class UpdateMultipleFieldsLiveTest {
+
+ private MongoClient mongoClient;
+ private MongoDatabase db;
+ private MongoCollection collection;
+
+ @Before
+ public void setup() {
+ if (mongoClient == null) {
+ mongoClient = new MongoClient("localhost", 27017);
+ db = mongoClient.getDatabase("baeldung");
+ collection = db.getCollection("employee");
+
+ collection.insertOne(Document.parse("{'employee_id':794875,'employee_name': 'David Smith','job': 'Sales Representative','department_id': 2,'salary': 20000,'hire_date': NumberLong(\"1643969311817\")}"));
+ }
+ }
+
+ @Test
+ public void updateMultipleFieldsUsingDBObject() {
+
+ BasicDBObject searchQuery = new BasicDBObject("employee_id", 794875);
+ BasicDBObject updateFields = new BasicDBObject();
+ updateFields.append("department_id", 4);
+ updateFields.append("job", "Sales Manager");
+ BasicDBObject setQuery = new BasicDBObject();
+ setQuery.append("$set", updateFields);
+
+ collection.updateMany(searchQuery, setQuery);
+
+ Document nameDoc = collection.find(Filters.eq("employee_id", 794875))
+ .first();
+ assertNotNull(nameDoc);
+ assertFalse(nameDoc.isEmpty());
+
+ String job = nameDoc.get("job", String.class);
+ assertNotNull(job);
+
+ Integer department_id = nameDoc.get("department_id", Integer.class);
+ assertNotNull(department_id);
+
+ }
+
+ @Test
+ public void updateMultipleFieldsUsingDocument() {
+
+ collection.updateMany(Filters.eq("employee_id", 794875), Updates.combine(Updates.set("department_id", 4), Updates.set("job", "Sales Manager")));
+
+ Document nameDoc = collection.find(Filters.eq("employee_id", 794875))
+ .first();
+ assertNotNull(nameDoc);
+ assertFalse(nameDoc.isEmpty());
+
+ String job = nameDoc.get("job", String.class);
+ assertNotNull(job);
+
+ Integer department_id = nameDoc.get("department_id", Integer.class);
+ assertNotNull(department_id);
+
+ }
+
+}
+
diff --git a/persistence-modules/pom.xml b/persistence-modules/pom.xml
index 152e58d57b..f8e3cb05e8 100644
--- a/persistence-modules/pom.xml
+++ b/persistence-modules/pom.xml
@@ -27,6 +27,7 @@
hbasehibernate5hibernate-mapping
+ hibernate-mapping-2hibernate-ogmhibernate-annotationshibernate-exceptions
@@ -42,6 +43,7 @@
java-jpa-2java-jpa-3java-mongodb
+ java-mongodb-2jnosqljooqjpa-hibernate-cascade-type
@@ -73,6 +75,7 @@
spring-data-jpa-crudspring-data-jpa-crud-2spring-data-jpa-enterprise
+ spring-data-jpa-enterprise-2spring-data-jpa-filteringspring-data-jpa-queryspring-data-jpa-query-2
@@ -102,4 +105,4 @@
42.2.20
-
\ No newline at end of file
+
diff --git a/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/SpringContextTest.java b/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/SpringContextTest.java
index 22bd5c6551..83b7b227b3 100644
--- a/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/SpringContextTest.java
+++ b/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/SpringContextTest.java
@@ -32,10 +32,10 @@ public class SpringContextTest {
public static final String KEYSPACE_ACTIVATE_QUERY = "USE testKeySpace;";
public static final String DATA_TABLE_NAME = "book";
-
+
@Autowired
private CassandraAdminOperations adminTemplate;
-
+
@BeforeClass
public static void startCassandraEmbedded() throws InterruptedException, TTransportException, ConfigurationException, IOException {
EmbeddedCassandraServerHelper.startEmbeddedCassandra();
@@ -47,14 +47,14 @@ public class SpringContextTest {
}
@Before
- public void createTable() throws InterruptedException, TTransportException, ConfigurationException, IOException {
- adminTemplate.createTable(true, CqlIdentifier.cqlId(DATA_TABLE_NAME), Book.class, new HashMap());
+ public void createTable() {
+ adminTemplate.createTable(true, CqlIdentifier.cqlId(DATA_TABLE_NAME), Book.class, new HashMap<>());
}
-
+
@Test
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
-
+
@After
public void dropTable() {
adminTemplate.dropTable(CqlIdentifier.cqlId(DATA_TABLE_NAME));
diff --git a/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/BookRepositoryIntegrationTest.java b/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/BookRepositoryLiveTest.java
similarity index 92%
rename from persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/BookRepositoryIntegrationTest.java
rename to persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/BookRepositoryLiveTest.java
index 55e968d6f2..d5758c3574 100644
--- a/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/BookRepositoryIntegrationTest.java
+++ b/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/BookRepositoryLiveTest.java
@@ -1,17 +1,15 @@
package com.baeldung.spring.data.cassandra.repository;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-
-import java.io.IOException;
-import java.util.HashMap;
-
+import com.baeldung.spring.data.cassandra.config.CassandraConfig;
import com.baeldung.spring.data.cassandra.model.Book;
+import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.utils.UUIDs;
+import com.google.common.collect.ImmutableSet;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.thrift.transport.TTransportException;
-import com.baeldung.spring.data.cassandra.config.CassandraConfig;
import org.cassandraunit.utils.EmbeddedCassandraServerHelper;
import org.junit.After;
import org.junit.AfterClass;
@@ -25,15 +23,24 @@ import org.springframework.data.cassandra.core.CassandraAdminOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import com.datastax.driver.core.Cluster;
-import com.datastax.driver.core.Session;
-import com.datastax.driver.core.utils.UUIDs;
-import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.HashMap;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+/**
+ * Live test for Cassandra testing.
+ *
+ * This can be converted to IntegrationTest once cassandra-unit tests can be executed in parallel and
+ * multiple test servers started as part of test suite.
+ *
+ * Open cassandra-unit issue for parallel execution: https://github.com/jsevellec/cassandra-unit/issues/155
+ */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CassandraConfig.class)
-public class BookRepositoryIntegrationTest {
- private static final Log LOGGER = LogFactory.getLog(BookRepositoryIntegrationTest.class);
+public class BookRepositoryLiveTest {
+ private static final Log LOGGER = LogFactory.getLog(BookRepositoryLiveTest.class);
public static final String KEYSPACE_CREATION_QUERY = "CREATE KEYSPACE IF NOT EXISTS testKeySpace WITH replication = { 'class': 'SimpleStrategy', 'replication_factor': '3' };";
@@ -47,8 +54,6 @@ public class BookRepositoryIntegrationTest {
@Autowired
private CassandraAdminOperations adminTemplate;
- //
-
@BeforeClass
public static void startCassandraEmbedded() throws InterruptedException, TTransportException, ConfigurationException, IOException {
EmbeddedCassandraServerHelper.startEmbeddedCassandra();
@@ -62,8 +67,8 @@ public class BookRepositoryIntegrationTest {
}
@Before
- public void createTable() throws InterruptedException, TTransportException, ConfigurationException, IOException {
- adminTemplate.createTable(true, CqlIdentifier.cqlId(DATA_TABLE_NAME), Book.class, new HashMap());
+ public void createTable() {
+ adminTemplate.createTable(true, CqlIdentifier.cqlId(DATA_TABLE_NAME), Book.class, new HashMap<>());
}
@Test
diff --git a/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/CassandraTemplateIntegrationTest.java b/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/CassandraTemplateLiveTest.java
similarity index 93%
rename from persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/CassandraTemplateIntegrationTest.java
rename to persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/CassandraTemplateLiveTest.java
index 90ae68ba98..bc05302d13 100644
--- a/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/CassandraTemplateIntegrationTest.java
+++ b/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/CassandraTemplateLiveTest.java
@@ -1,17 +1,13 @@
package com.baeldung.spring.data.cassandra.repository;
-import static junit.framework.TestCase.assertNull;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
import com.baeldung.spring.data.cassandra.config.CassandraConfig;
import com.baeldung.spring.data.cassandra.model.Book;
+import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.datastax.driver.core.querybuilder.Select;
+import com.datastax.driver.core.utils.UUIDs;
+import com.google.common.collect.ImmutableSet;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -30,17 +26,28 @@ import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import com.datastax.driver.core.Cluster;
-import com.datastax.driver.core.Session;
-import com.datastax.driver.core.querybuilder.QueryBuilder;
-import com.datastax.driver.core.querybuilder.Select;
-import com.datastax.driver.core.utils.UUIDs;
-import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import static junit.framework.TestCase.assertNull;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Live test for Cassandra testing.
+ *
+ * This can be converted to IntegrationTest once cassandra-unit tests can be executed in parallel and
+ * multiple test servers started as part of test suite.
+ *
+ * Open cassandra-unit issue for parallel execution: https://github.com/jsevellec/cassandra-unit/issues/155
+ */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CassandraConfig.class)
-public class CassandraTemplateIntegrationTest {
- private static final Log LOGGER = LogFactory.getLog(CassandraTemplateIntegrationTest.class);
+public class CassandraTemplateLiveTest {
+ private static final Log LOGGER = LogFactory.getLog(CassandraTemplateLiveTest.class);
public static final String KEYSPACE_CREATION_QUERY = "CREATE KEYSPACE IF NOT EXISTS testKeySpace " + "WITH replication = { 'class': 'SimpleStrategy', 'replication_factor': '3' };";
@@ -54,8 +61,6 @@ public class CassandraTemplateIntegrationTest {
@Autowired
private CassandraOperations cassandraTemplate;
- //
-
@BeforeClass
public static void startCassandraEmbedded() throws InterruptedException, TTransportException, ConfigurationException, IOException {
EmbeddedCassandraServerHelper.startEmbeddedCassandra();
@@ -69,8 +74,8 @@ public class CassandraTemplateIntegrationTest {
}
@Before
- public void createTable() throws InterruptedException, TTransportException, ConfigurationException, IOException {
- adminTemplate.createTable(true, CqlIdentifier.cqlId(DATA_TABLE_NAME), Book.class, new HashMap());
+ public void createTable() {
+ adminTemplate.createTable(true, CqlIdentifier.cqlId(DATA_TABLE_NAME), Book.class, new HashMap<>());
}
@Test
@@ -111,7 +116,7 @@ public class CassandraTemplateIntegrationTest {
}
@Test
- public void whenDeletingASelectedBook_thenNotAvailableOnRetrieval() throws InterruptedException {
+ public void whenDeletingASelectedBook_thenNotAvailableOnRetrieval() {
final Book javaBook = new Book(UUIDs.timeBased(), "Head First Java", "OReilly Media", ImmutableSet.of("Computer", "Software"));
cassandraTemplate.insert(javaBook);
cassandraTemplate.delete(javaBook);
diff --git a/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/CqlQueriesIntegrationTest.java b/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/CqlQueriesLiveTest.java
similarity index 90%
rename from persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/CqlQueriesIntegrationTest.java
rename to persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/CqlQueriesLiveTest.java
index f948218807..e1c67a1724 100644
--- a/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/CqlQueriesIntegrationTest.java
+++ b/persistence-modules/spring-data-cassandra/src/test/java/com/baeldung/spring/data/cassandra/repository/CqlQueriesLiveTest.java
@@ -1,19 +1,16 @@
package com.baeldung.spring.data.cassandra.repository;
-import static junit.framework.TestCase.assertEquals;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.UUID;
-
-import org.apache.cassandra.exceptions.ConfigurationException;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.thrift.transport.TTransportException;
import com.baeldung.spring.data.cassandra.config.CassandraConfig;
import com.baeldung.spring.data.cassandra.model.Book;
+import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.querybuilder.Insert;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.datastax.driver.core.querybuilder.Select;
+import com.datastax.driver.core.utils.UUIDs;
+import com.google.common.collect.ImmutableSet;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.cassandraunit.utils.EmbeddedCassandraServerHelper;
import org.junit.After;
import org.junit.AfterClass;
@@ -28,18 +25,25 @@ import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import com.datastax.driver.core.Cluster;
-import com.datastax.driver.core.Session;
-import com.datastax.driver.core.querybuilder.Insert;
-import com.datastax.driver.core.querybuilder.QueryBuilder;
-import com.datastax.driver.core.querybuilder.Select;
-import com.datastax.driver.core.utils.UUIDs;
-import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+import static junit.framework.TestCase.assertEquals;
+
+/**
+ * Live test for Cassandra testing.
+ *
+ * This can be converted to IntegrationTest once cassandra-unit tests can be executed in parallel and
+ * multiple test servers started as part of test suite.
+ *
+ * Open cassandra-unit issue for parallel execution: https://github.com/jsevellec/cassandra-unit/issues/155
+ */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CassandraConfig.class)
-public class CqlQueriesIntegrationTest {
- private static final Log LOGGER = LogFactory.getLog(CqlQueriesIntegrationTest.class);
+public class CqlQueriesLiveTest {
+ private static final Log LOGGER = LogFactory.getLog(CqlQueriesLiveTest.class);
public static final String KEYSPACE_CREATION_QUERY = "CREATE KEYSPACE IF NOT EXISTS testKeySpace " + "WITH replication = { 'class': 'SimpleStrategy', 'replication_factor': '3' };";
@@ -53,10 +57,8 @@ public class CqlQueriesIntegrationTest {
@Autowired
private CassandraOperations cassandraTemplate;
- //
-
@BeforeClass
- public static void startCassandraEmbedded() throws InterruptedException, TTransportException, ConfigurationException, IOException {
+ public static void startCassandraEmbedded() throws Exception {
EmbeddedCassandraServerHelper.startEmbeddedCassandra(25000);
final Cluster cluster = Cluster.builder().addContactPoints("127.0.0.1").withPort(9142).build();
LOGGER.info("Server Started at 127.0.0.1:9142... ");
@@ -68,8 +70,8 @@ public class CqlQueriesIntegrationTest {
}
@Before
- public void createTable() throws InterruptedException, TTransportException, ConfigurationException, IOException {
- adminTemplate.createTable(true, CqlIdentifier.cqlId(DATA_TABLE_NAME), Book.class, new HashMap());
+ public void createTable() {
+ adminTemplate.createTable(true, CqlIdentifier.cqlId(DATA_TABLE_NAME), Book.class, new HashMap<>());
}
@Test
diff --git a/persistence-modules/spring-data-dynamodb/pom.xml b/persistence-modules/spring-data-dynamodb/pom.xml
index 148215b68a..0e990c69f3 100644
--- a/persistence-modules/spring-data-dynamodb/pom.xml
+++ b/persistence-modules/spring-data-dynamodb/pom.xml
@@ -24,6 +24,13 @@
pomimport
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
@@ -184,6 +191,7 @@
https://s3-us-west-2.amazonaws.com/dynamodb-local/release3.1.12.4.7
+ 2.17.1
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jdbc/README.md b/persistence-modules/spring-data-jdbc/README.md
index 0e54d0ba88..b9ff9417a9 100644
--- a/persistence-modules/spring-data-jdbc/README.md
+++ b/persistence-modules/spring-data-jdbc/README.md
@@ -1,3 +1,4 @@
### Relevant Articles:
+- [Configure and Use Multiple DataSources in Spring Boot](https://www.baeldung.com/spring-boot-configure-multiple-datasources)
- [Introduction to Spring Data JDBC](https://www.baeldung.com/spring-data-jdbc-intro)
diff --git a/persistence-modules/spring-data-jdbc/pom.xml b/persistence-modules/spring-data-jdbc/pom.xml
index 2b4c6d21aa..630fe141b3 100644
--- a/persistence-modules/spring-data-jdbc/pom.xml
+++ b/persistence-modules/spring-data-jdbc/pom.xml
@@ -21,11 +21,19 @@
org.springframework.bootspring-boot-starter-data-jdbc
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+ com.h2databaseh2runtime
+
+ org.springframework.boot
+ spring-boot-starter-test
+
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/Application.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/Application.java
index 8fff82de32..a808a3daea 100644
--- a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/Application.java
+++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/Application.java
@@ -1,17 +1,16 @@
package com.baeldung.springdatajdbcintro;
-import java.util.Optional;
-
+import ch.qos.logback.classic.Logger;
+import com.baeldung.springdatajdbcintro.entity.Person;
+import com.baeldung.springdatajdbcintro.repository.PersonRepository;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.core.env.AbstractEnvironment;
-import com.baeldung.springdatajdbcintro.entity.Person;
-import com.baeldung.springdatajdbcintro.repository.PersonRepository;
-
-import ch.qos.logback.classic.Logger;
+import java.util.Optional;
@SpringBootApplication
public class Application implements CommandLineRunner {
@@ -24,6 +23,7 @@ public class Application implements CommandLineRunner {
private DatabaseSeeder dbSeeder;
public static void main(String[] args) {
+ System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "jdbcintro");
SpringApplication.run(Application.class, args);
}
diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/entity/Person.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/entity/Person.java
index 7c4c1eec73..ac2c329e08 100644
--- a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/entity/Person.java
+++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/entity/Person.java
@@ -1,7 +1,9 @@
package com.baeldung.springdatajdbcintro.entity;
import org.springframework.data.annotation.Id;
+import org.springframework.data.relational.core.mapping.Table;
+@Table
public class Person {
@Id
diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/repository/PersonRepository.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/repository/PersonRepository.java
index b2f026fa0c..ceb7a968a4 100644
--- a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/repository/PersonRepository.java
+++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/repository/PersonRepository.java
@@ -3,6 +3,7 @@ package com.baeldung.springdatajdbcintro.repository;
import com.baeldung.springdatajdbcintro.entity.Person;
import org.springframework.data.jdbc.repository.query.Modifying;
import org.springframework.data.jdbc.repository.query.Query;
+import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/Application.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/Application.java
new file mode 100644
index 0000000000..a08949585c
--- /dev/null
+++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/Application.java
@@ -0,0 +1,15 @@
+package com.baeldung.springmultipledatasources;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.core.env.AbstractEnvironment;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "multipledatasources");
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/todos/Todo.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/todos/Todo.java
new file mode 100644
index 0000000000..56f513027b
--- /dev/null
+++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/todos/Todo.java
@@ -0,0 +1,48 @@
+package com.baeldung.springmultipledatasources.todos;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class Todo {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ private String title;
+ private boolean completed;
+
+ public Todo() {
+ }
+
+ public Todo(String title) {
+ this.title = title;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public boolean isCompleted() {
+ return completed;
+ }
+
+ public void setCompleted(boolean completed) {
+ this.completed = completed;
+ }
+
+}
diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/todos/TodoDatasourceConfiguration.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/todos/TodoDatasourceConfiguration.java
new file mode 100644
index 0000000000..c599c26d01
--- /dev/null
+++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/todos/TodoDatasourceConfiguration.java
@@ -0,0 +1,29 @@
+package com.baeldung.springmultipledatasources.todos;
+
+import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+import javax.sql.DataSource;
+
+@Configuration
+public class TodoDatasourceConfiguration {
+
+ @Bean
+ @ConfigurationProperties("spring.datasource.todos")
+ public DataSourceProperties todosDataSourceProperties() {
+ return new DataSourceProperties();
+ }
+
+ @Bean
+ @Primary
+ @ConfigurationProperties("spring.datasource.todos.hikari")
+ public DataSource todosDataSource() {
+ return todosDataSourceProperties()
+ .initializeDataSourceBuilder()
+ .build();
+ }
+
+}
diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/todos/TodoJpaConfiguration.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/todos/TodoJpaConfiguration.java
new file mode 100644
index 0000000000..e1c4e1c9be
--- /dev/null
+++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/todos/TodoJpaConfiguration.java
@@ -0,0 +1,40 @@
+package com.baeldung.springmultipledatasources.todos;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+import java.util.Objects;
+
+@Configuration
+@EnableTransactionManagement
+@EnableJpaRepositories(
+ basePackageClasses = Todo.class,
+ entityManagerFactoryRef = "todosEntityManagerFactory",
+ transactionManagerRef = "todosTransactionManager"
+)
+public class TodoJpaConfiguration {
+
+ @Bean
+ public LocalContainerEntityManagerFactoryBean todosEntityManagerFactory(
+ @Qualifier("todosDataSource") DataSource dataSource,
+ EntityManagerFactoryBuilder builder) {
+ return builder
+ .dataSource(dataSource)
+ .packages(Todo.class)
+ .build();
+ }
+
+ @Bean
+ public PlatformTransactionManager todosTransactionManager(
+ @Qualifier("todosEntityManagerFactory") LocalContainerEntityManagerFactoryBean todosEntityManagerFactory) {
+ return new JpaTransactionManager(Objects.requireNonNull(todosEntityManagerFactory.getObject()));
+ }
+}
diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/todos/TodoRepository.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/todos/TodoRepository.java
new file mode 100644
index 0000000000..ebdd59551b
--- /dev/null
+++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/todos/TodoRepository.java
@@ -0,0 +1,6 @@
+package com.baeldung.springmultipledatasources.todos;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TodoRepository extends JpaRepository {
+}
diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/topics/Topic.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/topics/Topic.java
new file mode 100644
index 0000000000..390300ff1a
--- /dev/null
+++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/topics/Topic.java
@@ -0,0 +1,39 @@
+package com.baeldung.springmultipledatasources.topics;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class Topic {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ private String title;
+
+ public Topic() {
+ }
+
+ public Topic(String title) {
+ this.title = title;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+}
diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/topics/TopicDatasourceConfiguration.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/topics/TopicDatasourceConfiguration.java
new file mode 100644
index 0000000000..38daa13d7d
--- /dev/null
+++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/topics/TopicDatasourceConfiguration.java
@@ -0,0 +1,26 @@
+package com.baeldung.springmultipledatasources.topics;
+
+import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+
+@Configuration
+public class TopicDatasourceConfiguration {
+
+ @Bean
+ @ConfigurationProperties("spring.datasource.topics")
+ public DataSourceProperties topicsDataSourceProperties() {
+ return new DataSourceProperties();
+ }
+
+ @Bean
+ public DataSource topicsDataSource() {
+ return topicsDataSourceProperties()
+ .initializeDataSourceBuilder()
+ .build();
+ }
+
+}
diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/topics/TopicJpaConfiguration.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/topics/TopicJpaConfiguration.java
new file mode 100644
index 0000000000..a7daf54c07
--- /dev/null
+++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/topics/TopicJpaConfiguration.java
@@ -0,0 +1,41 @@
+package com.baeldung.springmultipledatasources.topics;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+import java.util.Objects;
+
+@Configuration
+@EnableTransactionManagement
+@EnableJpaRepositories(
+ basePackageClasses = Topic.class,
+ entityManagerFactoryRef = "topicsEntityManagerFactory",
+ transactionManagerRef = "topicsTransactionManager"
+)
+public class TopicJpaConfiguration {
+
+ @Bean
+ public LocalContainerEntityManagerFactoryBean topicsEntityManagerFactory(
+ @Qualifier("topicsDataSource") DataSource dataSource,
+ EntityManagerFactoryBuilder builder
+ ) {
+ return builder
+ .dataSource(dataSource)
+ .packages(Topic.class)
+ .build();
+ }
+
+ @Bean
+ public PlatformTransactionManager topicsTransactionManager(
+ @Qualifier("topicsEntityManagerFactory") LocalContainerEntityManagerFactoryBean topicsEntityManagerFactory) {
+ return new JpaTransactionManager(Objects.requireNonNull(topicsEntityManagerFactory.getObject()));
+ }
+}
diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/topics/TopicRepository.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/topics/TopicRepository.java
new file mode 100644
index 0000000000..5aebc3fe87
--- /dev/null
+++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springmultipledatasources/topics/TopicRepository.java
@@ -0,0 +1,6 @@
+package com.baeldung.springmultipledatasources.topics;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TopicRepository extends JpaRepository {
+}
diff --git a/persistence-modules/spring-data-jdbc/src/main/resources/application-jdbcintro.properties b/persistence-modules/spring-data-jdbc/src/main/resources/application-jdbcintro.properties
new file mode 100644
index 0000000000..4980c71be4
--- /dev/null
+++ b/persistence-modules/spring-data-jdbc/src/main/resources/application-jdbcintro.properties
@@ -0,0 +1,6 @@
+#H2 DB
+spring.jpa.hibernate.ddl-auto=none
+spring.datasource.url=jdbc:h2:mem:persondb
+spring.datasource.driverClassName=org.h2.Driver
+spring.datasource.username=sa
+spring.datasource.password=test
diff --git a/persistence-modules/spring-data-jdbc/src/main/resources/application-multipledatasources.properties b/persistence-modules/spring-data-jdbc/src/main/resources/application-multipledatasources.properties
new file mode 100644
index 0000000000..d9f859ea40
--- /dev/null
+++ b/persistence-modules/spring-data-jdbc/src/main/resources/application-multipledatasources.properties
@@ -0,0 +1,10 @@
+spring.jpa.hibernate.ddl-auto=update
+spring.datasource.todos.url=jdbc:h2:mem:todos
+spring.datasource.todos.username=sa
+spring.datasource.todos.password=null
+spring.datasource.todos.driverClassName=org.h2.Driver
+spring.datasource.todos.hikari.connectionTimeout=44444
+spring.datasource.topics.url=jdbc:h2:mem:topics
+spring.datasource.topics.username=sa
+spring.datasource.topics.password=null
+spring.datasource.topics.driverClassName=org.h2.Driver
diff --git a/persistence-modules/spring-data-jdbc/src/main/resources/application.properties b/persistence-modules/spring-data-jdbc/src/main/resources/application.properties
index 45099222fc..2a0c259c00 100644
--- a/persistence-modules/spring-data-jdbc/src/main/resources/application.properties
+++ b/persistence-modules/spring-data-jdbc/src/main/resources/application.properties
@@ -1,8 +1,3 @@
#H2 DB
-spring.jpa.hibernate.ddl-auto=none
spring.h2.console.enabled=true
-spring.datasource.url=jdbc:h2:mem:persondb
-spring.datasource.driverClassName=org.h2.Driver
-spring.datasource.username=sa
-spring.datasource.password=test
-spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
\ No newline at end of file
+spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
diff --git a/persistence-modules/spring-data-jdbc/src/test/java/com/baeldung/springmultipledatasources/MultipleDatasourcesIntegrationTest.java b/persistence-modules/spring-data-jdbc/src/test/java/com/baeldung/springmultipledatasources/MultipleDatasourcesIntegrationTest.java
new file mode 100644
index 0000000000..4d401396a2
--- /dev/null
+++ b/persistence-modules/spring-data-jdbc/src/test/java/com/baeldung/springmultipledatasources/MultipleDatasourcesIntegrationTest.java
@@ -0,0 +1,41 @@
+package com.baeldung.springmultipledatasources;
+
+import com.baeldung.springmultipledatasources.todos.Todo;
+import com.baeldung.springmultipledatasources.todos.TodoRepository;
+import com.baeldung.springmultipledatasources.topics.Topic;
+import com.baeldung.springmultipledatasources.topics.TopicRepository;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ActiveProfiles("multipledatasources")
+@DataJpaTest // no test database!
+class MultipleDatasourcesIntegrationTest {
+
+ @Autowired
+ TodoRepository todoRepo;
+ @Autowired
+ TopicRepository topicRepo;
+
+ @Test
+ void shouldSaveTodoToTodoDB() {
+ Todo todo = new Todo("test");
+ Todo saved =todoRepo.save(todo);
+ Optional result= todoRepo.findById(saved.getId());
+ assertThat(result).isPresent();
+ }
+
+ @Test
+ void shouldSaveTopicToTopicDB() {
+ Topic todo = new Topic("test");
+ Topic saved =topicRepo.save(todo);
+ Optional result= topicRepo.findById(saved.getId());
+ assertThat(result).isPresent();
+ }
+
+}
diff --git a/persistence-modules/spring-data-jpa-enterprise-2/README.md b/persistence-modules/spring-data-jpa-enterprise-2/README.md
new file mode 100644
index 0000000000..9bb08cc497
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-enterprise-2/README.md
@@ -0,0 +1,18 @@
+## Spring Data JPA - Enterprise
+
+This module contains articles about Spring Data JPA used in enterprise applications such as transactions, sessions, naming conventions and more
+
+### Relevant Articles:
+
+- [Spring JPA – Multiple Databases](https://www.baeldung.com/spring-data-jpa-multiple-databases)
+- [Pagination and Sorting using Spring Data JPA](https://www.baeldung.com/spring-data-jpa-pagination-sorting)
+
+### Eclipse Config
+After importing the project into Eclipse, you may see the following error:
+"No persistence xml file found in project"
+
+This can be ignored:
+- Project -> Properties -> Java Persistance -> JPA -> Error/Warnings -> Select Ignore on "No persistence xml file found in project"
+Or:
+- Eclipse -> Preferences - Validation - disable the "Build" execution of the JPA Validator
+
diff --git a/persistence-modules/spring-data-jpa-enterprise-2/pom.xml b/persistence-modules/spring-data-jpa-enterprise-2/pom.xml
new file mode 100644
index 0000000000..f35ee378d1
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-enterprise-2/pom.xml
@@ -0,0 +1,36 @@
+
+
+ 4.0.0
+ spring-data-jpa-enterprise-2
+ spring-data-jpa-enterprise-2
+
+
+ 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-data-jpa
+
+
+ com.h2database
+ h2
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+
+
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/Application.java b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/Application.java
new file mode 100644
index 0000000000..37dbe7dab8
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/Application.java
@@ -0,0 +1,13 @@
+package com.baeldung;
+
+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/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/MultipleDbApplication.java b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/MultipleDbApplication.java
similarity index 100%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/MultipleDbApplication.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/MultipleDbApplication.java
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/PersistenceProductAutoConfiguration.java b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/PersistenceProductAutoConfiguration.java
similarity index 97%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/PersistenceProductAutoConfiguration.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/PersistenceProductAutoConfiguration.java
index a6f8f0829f..bc7ab57b06 100644
--- a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/PersistenceProductAutoConfiguration.java
+++ b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/PersistenceProductAutoConfiguration.java
@@ -8,7 +8,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/PersistenceProductConfiguration.java b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/PersistenceProductConfiguration.java
similarity index 100%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/PersistenceProductConfiguration.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/PersistenceProductConfiguration.java
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/PersistenceUserAutoConfiguration.java b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/PersistenceUserAutoConfiguration.java
similarity index 97%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/PersistenceUserAutoConfiguration.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/PersistenceUserAutoConfiguration.java
index e04a1621b2..8080885b37 100644
--- a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/PersistenceUserAutoConfiguration.java
+++ b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/PersistenceUserAutoConfiguration.java
@@ -8,7 +8,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/PersistenceUserConfiguration.java b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/PersistenceUserConfiguration.java
similarity index 100%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/PersistenceUserConfiguration.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/PersistenceUserConfiguration.java
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/dao/product/ProductRepository.java b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/dao/product/ProductRepository.java
old mode 100755
new mode 100644
similarity index 100%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/dao/product/ProductRepository.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/dao/product/ProductRepository.java
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/dao/user/PossessionRepository.java b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/dao/user/PossessionRepository.java
similarity index 100%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/dao/user/PossessionRepository.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/dao/user/PossessionRepository.java
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/dao/user/UserRepository.java b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/dao/user/UserRepository.java
similarity index 100%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/dao/user/UserRepository.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/dao/user/UserRepository.java
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/model/product/Product.java b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/model/product/Product.java
old mode 100755
new mode 100644
similarity index 100%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/model/product/Product.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/model/product/Product.java
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/model/user/Possession.java b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/model/user/Possession.java
similarity index 100%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/model/user/Possession.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/model/user/Possession.java
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/model/user/User.java b/persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/model/user/User.java
similarity index 100%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/multipledb/model/user/User.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/java/com/baeldung/multipledb/model/user/User.java
diff --git a/persistence-modules/spring-data-jpa-enterprise-2/src/main/resources/application.properties b/persistence-modules/spring-data-jpa-enterprise-2/src/main/resources/application.properties
new file mode 100644
index 0000000000..0ca1872207
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-enterprise-2/src/main/resources/application.properties
@@ -0,0 +1,15 @@
+
+spring.datasource.url=jdbc:h2:mem:baeldung
+
+# JPA-Schema-Generation
+# Use below configuration to generate database schema create commands based on the entity models
+# and export them into the create.sql file
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=create.sql
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-source=metadata
+#spring.jpa.properties.hibernate.format_sql=true
+
+spring.jpa.show-sql=false
+
+#hibernate.dialect=org.hibernate.dialect.H2Dialect
+spring.jpa.properties.hibernate.id.new_generator_mappings=false
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/resources/persistence-multiple-db-boot.properties b/persistence-modules/spring-data-jpa-enterprise-2/src/main/resources/persistence-multiple-db-boot.properties
similarity index 100%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/resources/persistence-multiple-db-boot.properties
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/resources/persistence-multiple-db-boot.properties
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/resources/persistence-multiple-db.properties b/persistence-modules/spring-data-jpa-enterprise-2/src/main/resources/persistence-multiple-db.properties
similarity index 100%
rename from persistence-modules/spring-data-jpa-enterprise/src/main/resources/persistence-multiple-db.properties
rename to persistence-modules/spring-data-jpa-enterprise-2/src/main/resources/persistence-multiple-db.properties
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/multipledb/JpaMultipleDBIntegrationTest.java b/persistence-modules/spring-data-jpa-enterprise-2/src/test/java/com/baeldung/multipledb/JpaMultipleDBIntegrationTest.java
similarity index 100%
rename from persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/multipledb/JpaMultipleDBIntegrationTest.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/test/java/com/baeldung/multipledb/JpaMultipleDBIntegrationTest.java
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/multipledb/ProductRepositoryIntegrationTest.java b/persistence-modules/spring-data-jpa-enterprise-2/src/test/java/com/baeldung/multipledb/ProductRepositoryIntegrationTest.java
similarity index 97%
rename from persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/multipledb/ProductRepositoryIntegrationTest.java
rename to persistence-modules/spring-data-jpa-enterprise-2/src/test/java/com/baeldung/multipledb/ProductRepositoryIntegrationTest.java
index 9bfba61a3b..831790af95 100644
--- a/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/multipledb/ProductRepositoryIntegrationTest.java
+++ b/persistence-modules/spring-data-jpa-enterprise-2/src/test/java/com/baeldung/multipledb/ProductRepositoryIntegrationTest.java
@@ -18,12 +18,10 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
-import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
-import com.baeldung.multipledb.PersistenceProductConfiguration;
import com.baeldung.multipledb.dao.product.ProductRepository;
import com.baeldung.multipledb.model.product.Product;
diff --git a/persistence-modules/spring-data-jpa-enterprise-2/src/test/resources/logback-test.xml b/persistence-modules/spring-data-jpa-enterprise-2/src/test/resources/logback-test.xml
new file mode 100644
index 0000000000..1595326253
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-enterprise-2/src/test/resources/logback-test.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ [%d{ISO8601}]-[%thread] %-5level %logger - %msg%n
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-enterprise/README.md b/persistence-modules/spring-data-jpa-enterprise/README.md
index 42fbecc880..ca89bd54e7 100644
--- a/persistence-modules/spring-data-jpa-enterprise/README.md
+++ b/persistence-modules/spring-data-jpa-enterprise/README.md
@@ -4,9 +4,7 @@ This module contains articles about Spring Data JPA used in enterprise applicati
### Relevant Articles:
-- [Spring JPA – Multiple Databases](https://www.baeldung.com/spring-data-jpa-multiple-databases)
- [Spring Data Java 8 Support](https://www.baeldung.com/spring-data-java-8)
-- [Pagination and Sorting using Spring Data JPA](https://www.baeldung.com/spring-data-jpa-pagination-sorting)
- [DB Integration Tests with Spring Boot and Testcontainers](https://www.baeldung.com/spring-boot-testcontainers-integration-test)
- [A Guide to Spring’s Open Session In View](https://www.baeldung.com/spring-open-session-in-view)
- [Working with Lazy Element Collections in JPA](https://www.baeldung.com/java-jpa-lazy-collections)
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/SpringJpaContextIntegrationTest.java b/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/SpringJpaContextIntegrationTest.java
index f3697bf39f..ecaa83f3f3 100644
--- a/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/SpringJpaContextIntegrationTest.java
+++ b/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/SpringJpaContextIntegrationTest.java
@@ -8,14 +8,10 @@ import org.springframework.test.context.junit4.SpringRunner;
import com.baeldung.boot.Application;
import com.baeldung.boot.config.PersistenceConfiguration;
-import com.baeldung.multipledb.PersistenceProductConfiguration;
-import com.baeldung.multipledb.PersistenceUserConfiguration;
@RunWith(SpringRunner.class)
@DataJpaTest(excludeAutoConfiguration = {
- PersistenceConfiguration.class,
- PersistenceUserConfiguration.class,
- PersistenceProductConfiguration.class })
+ PersistenceConfiguration.class })
@ContextConfiguration(classes = Application.class)
public class SpringJpaContextIntegrationTest {
diff --git a/persistence-modules/spring-data-jpa-filtering/pom.xml b/persistence-modules/spring-data-jpa-filtering/pom.xml
index 287a3136fd..9c10f4a997 100644
--- a/persistence-modules/spring-data-jpa-filtering/pom.xml
+++ b/persistence-modules/spring-data-jpa-filtering/pom.xml
@@ -67,6 +67,7 @@
com.baeldung.boot.Application1.10.642.2.5
+ 2.6.1
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-query-3/pom.xml b/persistence-modules/spring-data-jpa-query-3/pom.xml
index 66a4486bc8..135d31aaba 100644
--- a/persistence-modules/spring-data-jpa-query-3/pom.xml
+++ b/persistence-modules/spring-data-jpa-query-3/pom.xml
@@ -29,4 +29,4 @@
-
+
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-repo-2/README.md b/persistence-modules/spring-data-jpa-repo-2/README.md
index be5bab1b56..6403510e6f 100644
--- a/persistence-modules/spring-data-jpa-repo-2/README.md
+++ b/persistence-modules/spring-data-jpa-repo-2/README.md
@@ -1,7 +1,9 @@
## Spring Data JPA - Repositories
### Relevant Articles:
+
- [Introduction to Spring Data JPA](https://www.baeldung.com/the-persistence-layer-with-spring-data-jpa)
- [Performance Difference Between save() and saveAll() in Spring Data](https://www.baeldung.com/spring-data-save-saveall)
- [LIKE Queries in Spring JPA Repositories](https://www.baeldung.com/spring-jpa-like-queries)
+- [How to Access EntityManager with Spring Data](https://www.baeldung.com/spring-data-entitymanager)
- More articles: [[<-- prev]](../spring-data-jpa-repo)
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/CustomRepositoryApplication.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/CustomRepositoryApplication.java
new file mode 100644
index 0000000000..f8f42aa67c
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/CustomRepositoryApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.spring.data.persistence.customrepository;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CustomRepositoryApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CustomRepositoryApplication.class, args);
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/model/User.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/model/User.java
new file mode 100644
index 0000000000..ba217577f3
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/model/User.java
@@ -0,0 +1,55 @@
+package com.baeldung.spring.data.persistence.customrepository.model;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import java.util.Objects;
+
+@Entity
+public class User {
+
+ @Id
+ @GeneratedValue
+ private Long id;
+ private String name;
+ private String email;
+
+ 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;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ User user = (User) o;
+ return Objects.equals(id, user.id) && Objects.equals(name, user.name) && Objects.equals(email, user.email);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, name, email);
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/repository/CustomUserRepository.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/repository/CustomUserRepository.java
new file mode 100644
index 0000000000..2c79e518b6
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/repository/CustomUserRepository.java
@@ -0,0 +1,9 @@
+package com.baeldung.spring.data.persistence.customrepository.repository;
+
+import com.baeldung.spring.data.persistence.customrepository.model.User;
+
+public interface CustomUserRepository {
+
+ User customFindMethod(Long id);
+
+}
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/repository/CustomUserRepositoryImpl.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/repository/CustomUserRepositoryImpl.java
new file mode 100644
index 0000000000..dc006580c1
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/repository/CustomUserRepositoryImpl.java
@@ -0,0 +1,26 @@
+package com.baeldung.spring.data.persistence.customrepository.repository;
+
+import com.baeldung.spring.data.persistence.customrepository.model.User;
+
+import javax.annotation.PostConstruct;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import java.util.Objects;
+
+public class CustomUserRepositoryImpl implements CustomUserRepository {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Override
+ public User customFindMethod(Long id) {
+ return (User) entityManager.createQuery("FROM User u WHERE u.id = :id")
+ .setParameter("id", id)
+ .getSingleResult();
+ }
+
+ @PostConstruct
+ public void postConstruct() {
+ Objects.requireNonNull(entityManager);
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/repository/UserRepository.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/repository/UserRepository.java
new file mode 100644
index 0000000000..d9f60015cc
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/customrepository/repository/UserRepository.java
@@ -0,0 +1,10 @@
+package com.baeldung.spring.data.persistence.customrepository.repository;
+
+import com.baeldung.spring.data.persistence.customrepository.model.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface UserRepository extends JpaRepository, CustomUserRepository {
+
+}
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/customrepository/CustomRepositoryUnitTest.java b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/customrepository/CustomRepositoryUnitTest.java
new file mode 100644
index 0000000000..ed54225a14
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/customrepository/CustomRepositoryUnitTest.java
@@ -0,0 +1,27 @@
+package com.baeldung.spring.data.persistence.customrepository;
+
+import com.baeldung.spring.data.persistence.customrepository.model.User;
+import com.baeldung.spring.data.persistence.customrepository.repository.UserRepository;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@SpringBootTest(classes = CustomRepositoryApplication.class)
+class CustomRepositoryUnitTest {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Test
+ void givenCustomRepository_whenInvokeCustomFindMethod_thenEntityIsFound() {
+ User user = new User();
+ user.setEmail("foo@gmail.com");
+ user.setName("userName");
+
+ User persistedUser = userRepository.save(user);
+
+ assertEquals(persistedUser, userRepository.customFindMethod(user.getId()));
+ }
+}
diff --git a/persistence-modules/spring-data-mongodb-reactive/pom.xml b/persistence-modules/spring-data-mongodb-reactive/pom.xml
index 0e4efed6e6..ac2bf14635 100644
--- a/persistence-modules/spring-data-mongodb-reactive/pom.xml
+++ b/persistence-modules/spring-data-mongodb-reactive/pom.xml
@@ -124,7 +124,7 @@
- 5.3.13
+ 5.3.154.5.23.3.1.RELEASE3.2.6
diff --git a/persistence-modules/spring-hibernate-5/README.md b/persistence-modules/spring-hibernate-5/README.md
index eff59a0362..9770fe95af 100644
--- a/persistence-modules/spring-hibernate-5/README.md
+++ b/persistence-modules/spring-hibernate-5/README.md
@@ -4,7 +4,6 @@ This module contains articles about Hibernate 5 with Spring.
### Relevant articles
-- [Hibernate Many to Many Annotation Tutorial](https://www.baeldung.com/hibernate-many-to-many)
- [Programmatic Transactions in the Spring TestContext Framework](https://www.baeldung.com/spring-test-programmatic-transactions)
- [JPA Criteria Queries](https://www.baeldung.com/hibernate-criteria-queries)
- [Introduction to Hibernate Search](https://www.baeldung.com/hibernate-search)
diff --git a/persistence-modules/spring-hibernate-5/pom.xml b/persistence-modules/spring-hibernate-5/pom.xml
index d0fa23504c..3f5d00733d 100644
--- a/persistence-modules/spring-hibernate-5/pom.xml
+++ b/persistence-modules/spring-hibernate-5/pom.xml
@@ -108,6 +108,21 @@
h2${h2.version}
+
+ com.sun.xml.bind
+ jaxb-core
+ ${com.sun.xml.version}
+
+
+ javax.xml.bind
+ jaxb-api
+ ${javax.xml.bind.version}
+
+
+ com.sun.xml.bind
+ jaxb-impl
+ ${com.sun.xml.version}
+
@@ -122,6 +137,8 @@
9.0.0.M261.12.3.4
+ 2.3.0.1
+ 2.3.1
\ No newline at end of file
diff --git a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/persistence/manytomany/dao/IEmployeeDao.java b/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/persistence/manytomany/dao/IEmployeeDao.java
deleted file mode 100644
index d619807b64..0000000000
--- a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/persistence/manytomany/dao/IEmployeeDao.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.baeldung.persistence.manytomany.dao;
-
-import com.baeldung.hibernate.manytomany.model.Employee;
-import com.baeldung.persistence.dao.common.IOperations;
-
-public interface IEmployeeDao extends IOperations{
-
-}
diff --git a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/persistence/manytomany/dao/IProjectDao.java b/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/persistence/manytomany/dao/IProjectDao.java
deleted file mode 100644
index 4a55714f8d..0000000000
--- a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/persistence/manytomany/dao/IProjectDao.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.baeldung.persistence.manytomany.dao;
-
-import com.baeldung.hibernate.manytomany.model.Project;
-import com.baeldung.persistence.dao.common.IOperations;
-
-public interface IProjectDao extends IOperations{
-
-}
diff --git a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/persistence/manytomany/dao/impl/EmployeeDao.java b/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/persistence/manytomany/dao/impl/EmployeeDao.java
deleted file mode 100644
index b062c00ff9..0000000000
--- a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/persistence/manytomany/dao/impl/EmployeeDao.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.baeldung.persistence.manytomany.dao.impl;
-
-import org.springframework.stereotype.Repository;
-import com.baeldung.hibernate.manytomany.model.Employee;
-import com.baeldung.persistence.dao.common.AbstractHibernateDao;
-import com.baeldung.persistence.manytomany.dao.IEmployeeDao;
-
-@Repository
-public class EmployeeDao extends AbstractHibernateDao implements IEmployeeDao {
-
- public EmployeeDao() {
- super();
-
- setClazz(Employee.class);
- }
-}
diff --git a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/persistence/manytomany/dao/impl/ProjectDao.java b/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/persistence/manytomany/dao/impl/ProjectDao.java
deleted file mode 100644
index 772026fbc1..0000000000
--- a/persistence-modules/spring-hibernate-5/src/main/java/com/baeldung/persistence/manytomany/dao/impl/ProjectDao.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.baeldung.persistence.manytomany.dao.impl;
-
-import org.springframework.stereotype.Repository;
-import com.baeldung.hibernate.manytomany.model.Project;
-import com.baeldung.persistence.dao.common.AbstractHibernateDao;
-import com.baeldung.persistence.manytomany.dao.IProjectDao;
-
-
-@Repository
-public class ProjectDao extends AbstractHibernateDao implements IProjectDao {
-
- public ProjectDao() {
- super();
-
- setClazz(Project.class);
- }
-}
diff --git a/persistence-modules/spring-hibernate-5/src/main/resources/import.sql b/persistence-modules/spring-hibernate-5/src/main/resources/import-db.sql
similarity index 100%
rename from persistence-modules/spring-hibernate-5/src/main/resources/import.sql
rename to persistence-modules/spring-hibernate-5/src/main/resources/import-db.sql
diff --git a/persistence-modules/spring-hibernate-5/src/main/resources/manytomany.cfg.xml b/persistence-modules/spring-hibernate-5/src/main/resources/manytomany.cfg.xml
deleted file mode 100644
index 315e2e3118..0000000000
--- a/persistence-modules/spring-hibernate-5/src/main/resources/manytomany.cfg.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
- com.mysql.jdbc.Driver
- tutorialmy5ql
- jdbc:mysql://localhost:3306/spring_hibernate_many_to_many
- tutorialuser
- org.hibernate.dialect.MySQLDialect
- thread
- false
-
-
diff --git a/persistence-modules/spring-hibernate-5/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationMainIntegrationTest.java b/persistence-modules/spring-hibernate-5/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationMainIntegrationTest.java
deleted file mode 100644
index 0073e181cc..0000000000
--- a/persistence-modules/spring-hibernate-5/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationMainIntegrationTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.baeldung.hibernate.manytomany;
-
-import static org.junit.Assert.assertNotNull;
-import static junit.framework.TestCase.assertEquals;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import org.hibernate.Session;
-import org.hibernate.SessionFactory;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import com.baeldung.hibernate.manytomany.util.HibernateUtil;
-import com.baeldung.hibernate.manytomany.model.Employee;
-import com.baeldung.hibernate.manytomany.model.Project;
-
-/**
- * Configured in: manytomany.cfg.xml
- */
-public class HibernateManyToManyAnnotationMainIntegrationTest {
- private static SessionFactory sessionFactory;
-
- private Session session;
-
- @BeforeClass
- public static void beforeTests() {
- sessionFactory = HibernateUtil.getSessionFactory();
- }
-
- @Before
- public void setUp() {
- session = sessionFactory.openSession();
- session.beginTransaction();
- }
-
- @Test
- public void givenData_whenInsert_thenCreatesMtoMrelationship() {
- String[] employeeData = { "Peter Oven", "Allan Norman" };
- String[] projectData = { "IT Project", "Networking Project" };
- Set projects = new HashSet();
-
- for (String proj : projectData) {
- projects.add(new Project(proj));
- }
-
- for (String emp : employeeData) {
- Employee employee = new Employee(emp.split(" ")[0], emp.split(" ")[1]);
- assertEquals(0, employee.getProjects().size());
- employee.setProjects(projects);
- session.persist(employee);
- assertNotNull(employee);
- }
- }
-
- @Test
- public void givenSession_whenRead_thenReturnsMtoMdata() {
- @SuppressWarnings("unchecked")
- List employeeList = session.createQuery("FROM Employee").list();
- assertNotNull(employeeList);
- for(Employee employee : employeeList) {
- assertNotNull(employee.getProjects());
- }
- }
-
- @After
- public void tearDown() {
- session.getTransaction()
- .commit();
- session.close();
- }
-
- @AfterClass
- public static void afterTests() {
- sessionFactory.close();
- }
-
-}
diff --git a/persistence-modules/spring-hibernate-5/src/test/resources/criteria.cfg.xml b/persistence-modules/spring-hibernate-5/src/test/resources/criteria.cfg.xml
index bc4fed9680..9ca9836a70 100644
--- a/persistence-modules/spring-hibernate-5/src/test/resources/criteria.cfg.xml
+++ b/persistence-modules/spring-hibernate-5/src/test/resources/criteria.cfg.xml
@@ -11,6 +11,7 @@
org.hibernate.dialect.H2Dialectcreate-drop
+ import-db.sqlfalse
diff --git a/persistence-modules/spring-hibernate-5/src/test/resources/import.sql b/persistence-modules/spring-hibernate-5/src/test/resources/import-db.sql
similarity index 99%
rename from persistence-modules/spring-hibernate-5/src/test/resources/import.sql
rename to persistence-modules/spring-hibernate-5/src/test/resources/import-db.sql
index 087d62d331..52c800f6b4 100644
--- a/persistence-modules/spring-hibernate-5/src/test/resources/import.sql
+++ b/persistence-modules/spring-hibernate-5/src/test/resources/import-db.sql
@@ -1,3 +1,4 @@
+
insert into item (item_id, item_name, item_desc, item_price) values(1,'item One', 'test 1', 35.12);
insert into item (item_id, item_name, item_desc, item_price) values(2,'Pogo stick', 'Pogo stick', 466.12);
diff --git a/persistence-modules/spring-mybatis/pom.xml b/persistence-modules/spring-mybatis/pom.xml
index eb0ebd723d..65a8581f97 100644
--- a/persistence-modules/spring-mybatis/pom.xml
+++ b/persistence-modules/spring-mybatis/pom.xml
@@ -78,7 +78,7 @@
- 5.3.13
+ 5.3.152.0.63.5.2
diff --git a/pom.xml b/pom.xml
index 6219bbc692..daaf9db6d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,9 +7,6 @@
com.baeldungparent-modules1.0.0-SNAPSHOT
-
- spring-5-webflux-2
- parent-modulespom
@@ -353,8 +350,9 @@
apache-cxfapache-kafkaapache-libraries
- apache-olingo/olingo2
+ apache-olingoapache-poi
+ apache-poi-2apache-rocketmqapache-shiroapache-spark
@@ -400,7 +398,6 @@
daggerdata-structuresddd
-
deeplearning4jdiscord4jdisruptor
@@ -422,6 +419,7 @@
graphql/graphql-java
+ graphql/graphql-dgsgrpcgsonguava-modules
@@ -431,7 +429,6 @@
hazelcasthelidonhttpclient
-
httpclient-simplehystrix
@@ -458,10 +455,12 @@
java-vavr-streamjava-websocketjavax-servlets
+ javax-servlets-2javaxvaljaxbjee-7jee-7-security
+ jakarta-eejerseyjgitjgroups
@@ -481,8 +480,8 @@
kubernetesksqldb
- language-interop
- libraries-2
+ language-interop
+ libraries-2libraries-3libraries-apache-commonslibraries-apache-commons-collections
@@ -518,12 +517,12 @@
micronautmicroprofilemsf4j
-
+ muleesbmustachemybatisnetflix-modules
-
+ nettyninjaopen-liberty
@@ -555,9 +554,9 @@
rxjava-observablesrxjava-operators
- atomikos
- reactive-systems
- slack
+ atomikos
+ reactive-systems
+ slack
@@ -652,6 +651,7 @@
spring-cucumberspring-data-rest
+ spring-data-rest-2spring-data-rest-querydslspring-dispring-di-2
@@ -838,8 +838,9 @@
apache-cxfapache-kafkaapache-libraries
- apache-olingo/olingo2
+ apache-olingoapache-poi
+ apache-poi-2apache-rocketmqapache-shiroapache-spark
@@ -854,6 +855,7 @@
atomixaws
+ aws-app-syncaws-lambdaaws-reactive
@@ -884,7 +886,6 @@
daggerdata-structuresddd
-
deeplearning4jdiscord4jdisruptor
@@ -906,6 +907,7 @@
graphql/graphql-java
+ graphql/graphql-dgsgrpcgsonguava-modules
@@ -915,7 +917,6 @@
hazelcasthelidonhttpclient
-
httpclient-simplehystrix
@@ -942,10 +943,12 @@
java-vavr-streamjava-websocketjavax-servlets
+ javax-servlets-2javaxvaljaxbjee-7jee-7-security
+ jakarta-eejerseyjgitjgroups
@@ -966,6 +969,7 @@
ksqldb
+ language-interoplibraries-2libraries-3
@@ -980,6 +984,7 @@
libraries-http-2libraries-iolibraries-primitive
+ libraries-rpclibraries-securitylibraries-serverlibraries-server-2
@@ -1002,12 +1007,12 @@
micronautmicroprofilemsf4j
-
+ muleesbmustachemybatisnetflix-modules
-
+ nettyninjaopen-liberty
@@ -1039,9 +1044,9 @@
rxjava-observablesrxjava-operators
- atomikos
- reactive-systems
- slack
+ atomikos
+ reactive-systems
+ slack
@@ -1095,6 +1100,7 @@
spring-5-reactive-oauthspring-5-reactive-securityspring-5-webflux
+ spring-5-webflux-2spring-reactivespring-activiti
@@ -1127,9 +1133,11 @@
spring-cucumberspring-data-rest
+ spring-data-rest-2spring-data-rest-querydslspring-dispring-di-2
+ spring-di-3spring-droolsspring-ejb
@@ -1167,6 +1175,8 @@
spring-static-resourcesspring-swagger-codegen
+ spring-threads
+
spring-vaultspring-vertx
@@ -1196,8 +1206,8 @@
wicketwildflyxml
+ xml-2xstream
-
@@ -1313,9 +1323,9 @@
- core-java-modules/core-java-9
- core-java-modules/core-java-9-improvements
- core-java-modules/core-java-9-jigsaw
+ core-java-modules/core-java-9
+ core-java-modules/core-java-9-improvements
+ core-java-modules/core-java-9-jigsawcore-java-modules/core-java-9-streamscore-java-modules/core-java-10
@@ -1325,24 +1335,35 @@
+
+
core-java-modules/core-java-collections-set
+ core-java-modules/core-java-collections-maps-4core-java-modules/core-java-date-operations-1core-java-modules/core-java-datetime-conversioncore-java-modules/core-java-datetime-stringcore-java-modules/core-java-io-conversions-2core-java-modules/core-java-jpmscore-java-modules/core-java-os
+ core-java-modules/core-java-string-algorithms-3core-java-modules/core-java-string-operations-3core-java-modules/core-java-string-operations-4core-java-modules/core-java-time-measurementscore-java-modules/core-java-networking-3core-java-modules/multimodulemavenproject
+ core-java-modules/core-java-strings
+ ddd-modules
+ docker
+ httpclient-2
+ libraries-concurrencypersistence-modules/sirixpersistence-modules/spring-data-cassandra-2quarkus-vs-springbootquarkus-jandexspring-boot-modules/spring-boot-cassandre
+ spring-boot-modules/spring-boot-cameltesting-modules/testing-assertions
+ persistence-modules/fauna
@@ -1381,25 +1402,35 @@
+
+
core-java-modules/core-java-collections-set
+ core-java-modules/core-java-collections-maps-4core-java-modules/core-java-date-operations-1core-java-modules/core-java-datetime-conversioncore-java-modules/core-java-datetime-stringcore-java-modules/core-java-io-conversions-2core-java-modules/core-java-jpmscore-java-modules/core-java-os
+ core-java-modules/core-java-string-algorithms-3core-java-modules/core-java-string-operations-3core-java-modules/core-java-string-operations-4core-java-modules/core-java-time-measurementscore-java-modules/core-java-networking-3core-java-modules/multimodulemavenprojectcore-java-modules/core-java-strings
+ ddd-modules
+ docker
+ httpclient-2
+ libraries-concurrencypersistence-modules/sirixpersistence-modules/spring-data-cassandra-2quarkus-vs-springbootquarkus-jandexspring-boot-modules/spring-boot-cassandre
+ spring-boot-modules/spring-boot-cameltesting-modules/testing-assertions
+ persistence-modules/fauna
@@ -1466,7 +1497,6 @@
0.0.13.12.23.0.0
-
3.13.01.18.201.4.200
diff --git a/quarkus-jandex/hello-sender-application-properties/pom.xml b/quarkus-jandex/hello-sender-application-properties/pom.xml
index f63bb9be81..6658123bee 100644
--- a/quarkus-jandex/hello-sender-application-properties/pom.xml
+++ b/quarkus-jandex/hello-sender-application-properties/pom.xml
@@ -23,4 +23,4 @@
-
+
\ No newline at end of file
diff --git a/quarkus-vs-springboot/quarkus-project/.mvn/wrapper/maven-wrapper.properties b/quarkus-vs-springboot/quarkus-project/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000000..8c79a83ae4
--- /dev/null
+++ b/quarkus-vs-springboot/quarkus-project/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/quarkus-vs-springboot/quarkus-project/mvnw b/quarkus-vs-springboot/quarkus-project/mvnw
new file mode 100755
index 0000000000..5643201c7d
--- /dev/null
+++ b/quarkus-vs-springboot/quarkus-project/mvnw
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/quarkus-vs-springboot/quarkus-project/mvnw.cmd b/quarkus-vs-springboot/quarkus-project/mvnw.cmd
new file mode 100644
index 0000000000..23b7079a3d
--- /dev/null
+++ b/quarkus-vs-springboot/quarkus-project/mvnw.cmd
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/quarkus-vs-springboot/spring-project/.mvn/wrapper/maven-wrapper.properties b/quarkus-vs-springboot/spring-project/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000000..8c79a83ae4
--- /dev/null
+++ b/quarkus-vs-springboot/spring-project/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/quarkus-vs-springboot/spring-project/mvnw b/quarkus-vs-springboot/spring-project/mvnw
new file mode 100755
index 0000000000..5643201c7d
--- /dev/null
+++ b/quarkus-vs-springboot/spring-project/mvnw
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/quarkus-vs-springboot/spring-project/mvnw.cmd b/quarkus-vs-springboot/spring-project/mvnw.cmd
new file mode 100644
index 0000000000..23b7079a3d
--- /dev/null
+++ b/quarkus-vs-springboot/spring-project/mvnw.cmd
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/quarkus-vs-springboot/spring-project/pom.xml b/quarkus-vs-springboot/spring-project/pom.xml
index bf524cd550..7f0fa4c8c6 100644
--- a/quarkus-vs-springboot/spring-project/pom.xml
+++ b/quarkus-vs-springboot/spring-project/pom.xml
@@ -187,6 +187,7 @@
110.11.0-RC1
+ 2.17.1
\ No newline at end of file
diff --git a/quarkus-vs-springboot/spring-project/src/main/resources/application.properties b/quarkus-vs-springboot/spring-project/src/main/resources/application.properties
index adc2f8b0b4..1d49b67fda 100644
--- a/quarkus-vs-springboot/spring-project/src/main/resources/application.properties
+++ b/quarkus-vs-springboot/spring-project/src/main/resources/application.properties
@@ -1,4 +1,4 @@
-spring.r2dbc.url=${DB_URL:'r2dbc:postgresql://localhost:5432/postgres'}
+spring.r2dbc.url=${DB_URL:r2dbc:postgresql://localhost:5432/postgres}
spring.r2dbc.username=postgres
spring.r2dbc.password=example
spring.r2dbc.pool.enabled=true
diff --git a/spring-4/pom.xml b/spring-4/pom.xml
index 41f55ea13b..5887bd43e2 100644
--- a/spring-4/pom.xml
+++ b/spring-4/pom.xml
@@ -74,10 +74,12 @@
org.apache.logging.log4jlog4j-api
+ ${log4j2.version}org.apache.logging.log4jlog4j-core
+ ${log4j2.version}org.easymock
@@ -115,6 +117,7 @@
3.62.4.04.0.3
+ 2.17.1
\ No newline at end of file
diff --git a/spring-5-data-reactive/pom.xml b/spring-5-data-reactive/pom.xml
index c145992737..24971c0289 100644
--- a/spring-5-data-reactive/pom.xml
+++ b/spring-5-data-reactive/pom.xml
@@ -14,6 +14,18 @@
../parent-boot-2
+
+
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
+
+
+
io.projectreactor
@@ -138,6 +150,7 @@
3.3.1.RELEASE2.2.6.RELEASE
+ 2.17.1
\ No newline at end of file
diff --git a/spring-5-reactive-3/pom.xml b/spring-5-reactive-3/pom.xml
index 89af34732f..fea72cc736 100644
--- a/spring-5-reactive-3/pom.xml
+++ b/spring-5-reactive-3/pom.xml
@@ -41,4 +41,5 @@
1.0.1.RELEASE
+
\ No newline at end of file
diff --git a/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/WebClientStatusCodeHandler.java b/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/WebClientStatusCodeHandler.java
index 9594ca32f1..784fcf2812 100644
--- a/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/WebClientStatusCodeHandler.java
+++ b/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/WebClientStatusCodeHandler.java
@@ -1,7 +1,7 @@
package com.baeldung.webclient.status;
-import com.baeldung.webclient.status.exception.BadRequestException;
-import com.baeldung.webclient.status.exception.ServerErrorException;
+import com.baeldung.webclient.status.exception.CustomBadRequestException;
+import com.baeldung.webclient.status.exception.CustomServerErrorException;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
@@ -32,10 +32,10 @@ public class WebClientStatusCodeHandler {
.retrieve()
.onStatus(
HttpStatus.INTERNAL_SERVER_ERROR::equals,
- response -> response.bodyToMono(String.class).map(ServerErrorException::new))
+ response -> response.bodyToMono(String.class).map(CustomServerErrorException::new))
.onStatus(
HttpStatus.BAD_REQUEST::equals,
- response -> response.bodyToMono(String.class).map(BadRequestException::new))
+ response -> response.bodyToMono(String.class).map(CustomBadRequestException::new))
.bodyToMono(String.class);
}
@@ -43,11 +43,11 @@ public class WebClientStatusCodeHandler {
HttpStatus status = response.statusCode();
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
return response.bodyToMono(String.class)
- .flatMap(body -> Mono.error(new ServerErrorException(body)));
+ .flatMap(body -> Mono.error(new CustomServerErrorException(body)));
}
if (HttpStatus.BAD_REQUEST.equals(status)) {
return response.bodyToMono(String.class)
- .flatMap(body -> Mono.error(new BadRequestException(body)));
+ .flatMap(body -> Mono.error(new CustomBadRequestException(body)));
}
return Mono.just(response);
}
diff --git a/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/exception/BadRequestException.java b/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/exception/BadRequestException.java
deleted file mode 100644
index bf5c599805..0000000000
--- a/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/exception/BadRequestException.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.baeldung.webclient.status.exception;
-
-public class BadRequestException extends Exception {
- public BadRequestException(String message) {
- super(message);
- }
-}
diff --git a/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/exception/CustomBadRequestException.java b/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/exception/CustomBadRequestException.java
new file mode 100644
index 0000000000..ddc814b2d4
--- /dev/null
+++ b/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/exception/CustomBadRequestException.java
@@ -0,0 +1,7 @@
+package com.baeldung.webclient.status.exception;
+
+public class CustomBadRequestException extends Exception {
+ public CustomBadRequestException(String message) {
+ super(message);
+ }
+}
diff --git a/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/exception/CustomServerErrorException.java b/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/exception/CustomServerErrorException.java
new file mode 100644
index 0000000000..12adbc94e4
--- /dev/null
+++ b/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/exception/CustomServerErrorException.java
@@ -0,0 +1,7 @@
+package com.baeldung.webclient.status.exception;
+
+public class CustomServerErrorException extends Exception {
+ public CustomServerErrorException(String message) {
+ super(message);
+ }
+}
diff --git a/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/exception/ServerErrorException.java b/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/exception/ServerErrorException.java
deleted file mode 100644
index 7e97f17dff..0000000000
--- a/spring-5-reactive-client/src/main/java/com/baeldung/webclient/status/exception/ServerErrorException.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.baeldung.webclient.status.exception;
-
-public class ServerErrorException extends Exception {
- public ServerErrorException(String message) {
- super(message);
- }
-}
diff --git a/spring-activiti/pom.xml b/spring-activiti/pom.xml
index c685207cc4..898f88285b 100644
--- a/spring-activiti/pom.xml
+++ b/spring-activiti/pom.xml
@@ -17,6 +17,18 @@
../parent-boot-1
+
+
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
+
+
+
org.activiti
@@ -59,6 +71,7 @@
6.0.0
+ 2.17.1
\ No newline at end of file
diff --git a/spring-boot-modules/pom.xml b/spring-boot-modules/pom.xml
index ac0deaa9e5..fabd54aa92 100644
--- a/spring-boot-modules/pom.xml
+++ b/spring-boot-modules/pom.xml
@@ -36,7 +36,6 @@
spring-boot-deploymentspring-boot-dispring-boot-disable-logging
- spring-boot-camelspring-boot-ci-cdspring-boot-custom-starter
@@ -51,7 +50,9 @@
spring-boot-librariesspring-boot-libraries-2
+ spring-boot-libraries-comparisonspring-boot-logging-log4j2
+ spring-boot-multiple-datasourcesspring-boot-mvcspring-boot-mvc-2spring-boot-mvc-3
diff --git a/spring-boot-modules/spring-boot-1/pom.xml b/spring-boot-modules/spring-boot-1/pom.xml
index b6e3717f7c..594e6459a4 100644
--- a/spring-boot-modules/spring-boot-1/pom.xml
+++ b/spring-boot-modules/spring-boot-1/pom.xml
@@ -68,7 +68,7 @@
- 2.14.1
+ 2.17.1
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-2/pom.xml b/spring-boot-modules/spring-boot-2/pom.xml
index 65bacb7c47..0da07eaf00 100644
--- a/spring-boot-modules/spring-boot-2/pom.xml
+++ b/spring-boot-modules/spring-boot-2/pom.xml
@@ -62,8 +62,8 @@
- 2.14.1
- 5.3.13
+ 2.17.1
+ 5.3.151111
diff --git a/spring-boot-modules/spring-boot-actuator/README.md b/spring-boot-modules/spring-boot-actuator/README.md
index 59f7e929da..ea43377ed2 100644
--- a/spring-boot-modules/spring-boot-actuator/README.md
+++ b/spring-boot-modules/spring-boot-actuator/README.md
@@ -12,3 +12,4 @@ The "REST With Spring" Classes: http://bit.ly/restwithspring
- [Health Indicators in Spring Boot](https://www.baeldung.com/spring-boot-health-indicators)
- [How to Enable All Endpoints in Spring Boot Actuator](https://www.baeldung.com/spring-boot-actuator-enable-endpoints)
- [Spring Boot Startup Actuator Endpoint](https://www.baeldung.com/spring-boot-actuator-startup)
+- [Metrics for your Spring REST API](https://www.baeldung.com/spring-rest-api-metrics)
diff --git a/spring-boot-modules/spring-boot-actuator/pom.xml b/spring-boot-modules/spring-boot-actuator/pom.xml
index b2c7a4d28e..1ccf436bbf 100644
--- a/spring-boot-modules/spring-boot-actuator/pom.xml
+++ b/spring-boot-modules/spring-boot-actuator/pom.xml
@@ -23,6 +23,10 @@
org.springframework.bootspring-boot-starter-web
+
+ org.apache.tomcat.embed
+ tomcat-embed-jasper
+ org.springframework.bootspring-boot-starter-data-jpa
@@ -35,6 +39,16 @@
com.h2databaseh2
+
+ javax.servlet
+ javax.servlet-api
+ provided
+
+
+ javax.servlet
+ jstl
+ runtime
+ org.springframework.bootspring-boot-starter-test
@@ -51,6 +65,10 @@
spring-security-testtest
+
+ org.awaitility
+ awaitility
+
diff --git a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/MetricsApplication.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/MetricsApplication.java
new file mode 100644
index 0000000000..729b3c0b96
--- /dev/null
+++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/MetricsApplication.java
@@ -0,0 +1,42 @@
+package com.baeldung.metrics;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.security.servlet.SecurityRequestMatchersManagementContextConfiguration;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration;
+import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
+import org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration;
+import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.web.context.request.RequestContextListener;
+
+import javax.servlet.ServletContext;
+
+@EnableScheduling
+@ComponentScan("com.baeldung.metrics")
+@SpringBootApplication
+public class MetricsApplication extends SpringBootServletInitializer {
+
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+ return application.sources(MetricsApplication.class);
+ }
+
+ @Override
+ public void onStartup(ServletContext sc) {
+ // Manages the lifecycle of the root application context
+ sc.addListener(new RequestContextListener());
+ }
+
+ public static void main(final String[] args) {
+ // only load properties for this application
+ System.setProperty("spring.config.location", "classpath:application-metrics.properties");
+ SpringApplication.run(MetricsApplication.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/spring/WebConfig.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/WebConfig.java
similarity index 81%
rename from spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/spring/WebConfig.java
rename to spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/WebConfig.java
index e35acb0bf0..4c38e4dbad 100644
--- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/spring/WebConfig.java
+++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/WebConfig.java
@@ -1,4 +1,4 @@
-package com.baeldung.spring;
+package com.baeldung.metrics;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
@@ -10,14 +10,10 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
-@ComponentScan("com.baeldung.web")
+@ComponentScan("com.baeldung.metrics")
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
- public WebConfig() {
- super();
- }
-
@Bean
public ViewResolver viewResolver() {
final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
@@ -26,11 +22,10 @@ public class WebConfig implements WebMvcConfigurer {
return viewResolver;
}
- // API
@Override
public void addViewControllers(final ViewControllerRegistry registry) {
- registry.addViewController("/graph.html");
- registry.addViewController("/homepage.html");
+ registry.addViewController("/metrics/graph.html");
+ registry.addViewController("/metrics/homepage.html");
}
}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/controller/MetricsController.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/controller/MetricsController.java
new file mode 100644
index 0000000000..e52ddd70f1
--- /dev/null
+++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/controller/MetricsController.java
@@ -0,0 +1,41 @@
+package com.baeldung.metrics.controller;
+
+import com.baeldung.metrics.service.InMemoryMetricService;
+import com.baeldung.metrics.service.MetricService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import java.util.Map;
+
+@Controller
+@RequestMapping(value = "/metrics")
+@ResponseBody
+public class MetricsController {
+
+ @Autowired
+ private InMemoryMetricService metricService;
+
+ // change the qualifier to use the in-memory implementation
+ @Autowired
+ @Qualifier("customActuatorMetricService")
+ private MetricService graphMetricService;
+
+ @GetMapping(value = "/metric")
+ public Map> getMetric() {
+ return metricService.getFullMetric();
+ }
+
+ @GetMapping(value = "/status-metric")
+ public Map getStatusMetric() {
+ return metricService.getStatusMetric();
+ }
+
+ @GetMapping(value = "/metric-graph-data")
+ public Object[][] getMetricData() {
+ return graphMetricService.getGraphData();
+ }
+}
diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/MetricFilter.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/filter/MetricFilter.java
similarity index 64%
rename from spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/MetricFilter.java
rename to spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/filter/MetricFilter.java
index dee63b226f..0f7579f060 100644
--- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/MetricFilter.java
+++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/filter/MetricFilter.java
@@ -1,4 +1,11 @@
-package com.baeldung.web.metric;
+package com.baeldung.metrics.filter;
+
+import com.baeldung.metrics.service.CustomActuatorMetricService;
+import com.baeldung.metrics.service.InMemoryMetricService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -9,24 +16,23 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-import org.springframework.web.context.support.WebApplicationContextUtils;
-
@Component
public class MetricFilter implements Filter {
@Autowired
- private IMetricService metricService;
+ private InMemoryMetricService metricService;
@Autowired
- private ICustomActuatorMetricService actMetricService;
+ private CustomActuatorMetricService actMetricService;
@Override
- public void init(final FilterConfig config) throws ServletException {
+ public void init(final FilterConfig config) {
if (metricService == null || actMetricService == null) {
- metricService = (IMetricService) WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext()).getBean("metricService");
- actMetricService = WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext()).getBean(CustomActuatorMetricService.class);
+ WebApplicationContext appContext = WebApplicationContextUtils
+ .getRequiredWebApplicationContext(config.getServletContext());
+
+ metricService = appContext.getBean(InMemoryMetricService.class);
+ actMetricService = appContext.getBean(CustomActuatorMetricService.class);
}
}
@@ -42,8 +48,4 @@ public class MetricFilter implements Filter {
actMetricService.increaseCount(status);
}
- @Override
- public void destroy() {
-
- }
}
diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/ActuatorMetricService.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/ActuatorMetricService.java
similarity index 61%
rename from spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/ActuatorMetricService.java
rename to spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/ActuatorMetricService.java
index 8c26fa04a0..3eef265c02 100644
--- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/ActuatorMetricService.java
+++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/ActuatorMetricService.java
@@ -1,32 +1,31 @@
-package com.baeldung.web.metric;
+package com.baeldung.metrics.service;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.MeterRegistry;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Service;
-
-import io.micrometer.core.instrument.Counter;
-import io.micrometer.core.instrument.Meter;
-import io.micrometer.core.instrument.MeterRegistry;
-
@Service
-public class ActuatorMetricService implements IActuatorMetricService {
+public class ActuatorMetricService implements MetricService {
+
+ private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm");
@Autowired
private MeterRegistry publicMetrics;
- private final List> statusMetricsByMinute;
+ private final List> statusMetricsByMinute;
private final List statusList;
- private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public ActuatorMetricService() {
- super();
- statusMetricsByMinute = new ArrayList>();
- statusList = new ArrayList();
+ statusMetricsByMinute = new ArrayList<>();
+ statusList = new ArrayList<>();
}
@Override
@@ -36,19 +35,19 @@ public class ActuatorMetricService implements IActuatorMetricService {
final int rowCount = statusMetricsByMinute.size() + 1;
final Object[][] result = new Object[rowCount][colCount];
result[0][0] = "Time";
- int j = 1;
+ int j = 1;
for (final String status : statusList) {
result[0][j] = status;
j++;
}
for (int i = 1; i < rowCount; i++) {
- result[i][0] = dateFormat.format(new Date(current.getTime() - (60000 * (rowCount - i))));
+ result[i][0] = DATE_FORMAT.format(new Date(current.getTime() - (60000L * (rowCount - i))));
}
List minuteOfStatuses;
- List last = new ArrayList();
+ List last = new ArrayList<>();
for (int i = 1; i < rowCount; i++) {
minuteOfStatuses = statusMetricsByMinute.get(i - 1);
@@ -64,11 +63,9 @@ public class ActuatorMetricService implements IActuatorMetricService {
return result;
}
- // Non - API
-
- @Scheduled(fixedDelay = 60000)
+ @Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds:60000}")
private void exportMetrics() {
- final ArrayList lastMinuteStatuses = initializeStatuses(statusList.size());
+ final List lastMinuteStatuses = initializeStatuses(statusList.size());
for (final Meter counterMetric : publicMetrics.getMeters()) {
updateMetrics(counterMetric, lastMinuteStatuses);
@@ -77,34 +74,32 @@ public class ActuatorMetricService implements IActuatorMetricService {
statusMetricsByMinute.add(lastMinuteStatuses);
}
- private ArrayList initializeStatuses(final int size) {
- final ArrayList counterList = new ArrayList();
+ private List initializeStatuses(int size) {
+ List counterList = new ArrayList<>();
for (int i = 0; i < size; i++) {
counterList.add(0);
}
return counterList;
}
- private void updateMetrics(final Meter counterMetric, final ArrayList statusCount) {
- String status = "";
- int index = -1;
- int oldCount = 0;
+ private void updateMetrics(Meter counterMetric, List statusCount) {
- if (counterMetric.getId().getName().contains("counter.status.")) {
- status = counterMetric.getId().getName().substring(15, 18); // example 404, 200
+ String metricName = counterMetric.getId().getName();
+
+ if (metricName.contains("counter.status.")) {
+ // example 404, 200
+ String status = metricName.substring(15, 18);
appendStatusIfNotExist(status, statusCount);
- index = statusList.indexOf(status);
- oldCount = statusCount.get(index) == null ? 0 : statusCount.get(index);
+ int index = statusList.indexOf(status);
+ int oldCount = statusCount.get(index) == null ? 0 : statusCount.get(index);
statusCount.set(index, (int)((Counter) counterMetric).count() + oldCount);
}
}
- private void appendStatusIfNotExist(final String status, final ArrayList statusCount) {
+ private void appendStatusIfNotExist(String status, List statusCount) {
if (!statusList.contains(status)) {
statusList.add(status);
statusCount.add(0);
}
}
-
- //
-}
\ No newline at end of file
+}
diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/CustomActuatorMetricService.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/CustomActuatorMetricService.java
similarity index 68%
rename from spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/CustomActuatorMetricService.java
rename to spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/CustomActuatorMetricService.java
index ee17825b7c..9b4ccaa875 100644
--- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/CustomActuatorMetricService.java
+++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/CustomActuatorMetricService.java
@@ -1,40 +1,36 @@
-package com.baeldung.web.metric;
+package com.baeldung.metrics.service;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.search.Search;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Service;
-
-import io.micrometer.core.instrument.Counter;
-import io.micrometer.core.instrument.MeterRegistry;
-import io.micrometer.core.instrument.search.Search;
-
@Service
-public class CustomActuatorMetricService implements ICustomActuatorMetricService {
+public class CustomActuatorMetricService implements MetricService {
+
+ private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm");
@Autowired
private MeterRegistry registry;
- private final List> statusMetricsByMinute;
+ private final List> statusMetricsByMinute;
private final List statusList;
- private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public CustomActuatorMetricService() {
- super();
- statusMetricsByMinute = new ArrayList>();
- statusList = new ArrayList();
+ statusMetricsByMinute = new ArrayList<>();
+ statusList = new ArrayList<>();
}
- // API
-
- @Override
- public void increaseCount(final int status) {
+ public void increaseCount(int status) {
String counterName = "counter.status." + status;
- registry.counter(counterName).increment(1);
+ registry.counter(counterName).increment();
if (!statusList.contains(counterName)) {
statusList.add(counterName);
}
@@ -55,7 +51,7 @@ public class CustomActuatorMetricService implements ICustomActuatorMetricService
}
for (int i = 1; i < rowCount; i++) {
- result[i][0] = dateFormat.format(new Date(current.getTime() - (60000 * (rowCount - i))));
+ result[i][0] = DATE_FORMAT.format(new Date(current.getTime() - (60000L * (rowCount - i))));
}
List minuteOfStatuses;
@@ -72,19 +68,17 @@ public class CustomActuatorMetricService implements ICustomActuatorMetricService
return result;
}
- // Non - API
-
- @Scheduled(fixedDelay = 60000)
+ @Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds:60000}")
private void exportMetrics() {
- final ArrayList statusCount = new ArrayList();
+ List statusCount = new ArrayList<>();
for (final String status : statusList) {
Search search = registry.find(status);
- if (search != null) {
- Counter counter = search.counter();
- statusCount.add(counter != null ? ((int) counter.count()) : 0);
- registry.remove(counter);
- } else {
+ Counter counter = search.counter();
+ if (counter == null) {
statusCount.add(0);
+ } else {
+ statusCount.add((int) counter.count());
+ registry.remove(counter);
}
}
statusMetricsByMinute.add(statusCount);
diff --git a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/InMemoryMetricService.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/InMemoryMetricService.java
new file mode 100644
index 0000000000..0be5c21727
--- /dev/null
+++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/InMemoryMetricService.java
@@ -0,0 +1,112 @@
+package com.baeldung.metrics.service;
+
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+public class InMemoryMetricService implements MetricService {
+
+ private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+
+ private final Map> metricMap;
+ private final Map statusMetric;
+ private final Map> timeMap;
+
+ public InMemoryMetricService() {
+ metricMap = new ConcurrentHashMap<>();
+ statusMetric = new ConcurrentHashMap<>();
+ timeMap = new ConcurrentHashMap<>();
+ }
+
+ public void increaseCount(String request, int status) {
+ increaseMainMetric(request, status);
+ increaseStatusMetric(status);
+ updateTimeMap(status);
+ }
+
+ public Map> getFullMetric() {
+ return metricMap;
+ }
+
+ public Map getStatusMetric() {
+ return statusMetric;
+ }
+
+ public Object[][] getGraphData() {
+ final int colCount = statusMetric.keySet().size() + 1;
+ final Set allStatus = statusMetric.keySet();
+ final int rowCount = timeMap.keySet().size() + 1;
+
+ final Object[][] result = new Object[rowCount][colCount];
+ result[0][0] = "Time";
+
+ int j = 1;
+ for (final int status : allStatus) {
+ result[0][j] = status;
+ j++;
+ }
+ int i = 1;
+ Map tempMap;
+ for (final Entry> entry : timeMap.entrySet()) {
+ result[i][0] = entry.getKey();
+ tempMap = entry.getValue();
+ for (j = 1; j < colCount; j++) {
+ result[i][j] = tempMap.get((Integer) result[0][j]);
+ if (result[i][j] == null) {
+ result[i][j] = 0;
+ }
+ }
+ i++;
+ }
+
+ for (int k = 1; k < result[0].length; k++) {
+ result[0][k] = result[0][k].toString();
+ }
+
+ return result;
+ }
+
+ private void increaseMainMetric(String request, int status) {
+ Map statusMap = metricMap.get(request);
+ if (statusMap == null) {
+ statusMap = new ConcurrentHashMap<>();
+ }
+
+ Integer count = statusMap.get(status);
+ if (count == null) {
+ count = 1;
+ } else {
+ count++;
+ }
+ statusMap.put(status, count);
+ metricMap.put(request, statusMap);
+ }
+
+ private void increaseStatusMetric(int status) {
+ statusMetric.merge(status, 1, Integer::sum);
+ }
+
+ private void updateTimeMap(int status) {
+ final String time = DATE_FORMAT.format(new Date());
+ Map statusMap = timeMap.get(time);
+ if (statusMap == null) {
+ statusMap = new ConcurrentHashMap<>();
+ }
+
+ Integer count = statusMap.get(status);
+ if (count == null) {
+ count = 1;
+ } else {
+ count++;
+ }
+ statusMap.put(status, count);
+ timeMap.put(time, statusMap);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/MetricService.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/MetricService.java
new file mode 100644
index 0000000000..3642102b67
--- /dev/null
+++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/MetricService.java
@@ -0,0 +1,7 @@
+package com.baeldung.metrics.service;
+
+public interface MetricService {
+
+ Object[][] getGraphData();
+
+}
diff --git a/spring-boot-modules/spring-boot-actuator/src/main/resources/application-metrics.properties b/spring-boot-modules/spring-boot-actuator/src/main/resources/application-metrics.properties
new file mode 100644
index 0000000000..5f753a0c62
--- /dev/null
+++ b/spring-boot-modules/spring-boot-actuator/src/main/resources/application-metrics.properties
@@ -0,0 +1,9 @@
+management.endpoints.web.exposure.include=info,health,metrics
+
+# JPA and Security is not required for Metrics application
+spring.autoconfigure.exclude= org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, \
+ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, \
+ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, \
+ org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, \
+ org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration, \
+ org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration
diff --git a/spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/api-servlet.xml b/spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/api-servlet.xml
similarity index 100%
rename from spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/api-servlet.xml
rename to spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/api-servlet.xml
diff --git a/spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/view/graph.jsp b/spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/view/metrics/graph.jsp
similarity index 93%
rename from spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/view/graph.jsp
rename to spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/view/metrics/graph.jsp
index e1d5fdc987..75976557a0 100644
--- a/spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/view/graph.jsp
+++ b/spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/view/metrics/graph.jsp
@@ -11,7 +11,7 @@
});
function drawChart() {
- $.get("",
+ $.get("",
function(mydata) {
var data = google.visualization.arrayToDataTable(mydata);
diff --git a/spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/view/homepage.jsp b/spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/view/metrics/homepage.jsp
similarity index 100%
rename from spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/view/homepage.jsp
rename to spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/view/metrics/homepage.jsp
diff --git a/spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/web.xml b/spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/web.xml
similarity index 91%
rename from spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/web.xml
rename to spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/web.xml
index 7129b6b4af..84566ba93d 100644
--- a/spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/web.xml
+++ b/spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/web.xml
@@ -16,7 +16,7 @@
contextConfigLocation
- com.baeldung.spring
+ com.baeldung.metrics
@@ -37,7 +37,7 @@
metricFilter
- com.baeldung.web.metric.MetricFilter
+ com.baeldung.metrics.filter.MetricFilter
diff --git a/spring-boot-modules/spring-boot-actuator/src/test/java/com/baeldung/metrics/MetricsApplicationIntegrationTest.java b/spring-boot-modules/spring-boot-actuator/src/test/java/com/baeldung/metrics/MetricsApplicationIntegrationTest.java
new file mode 100644
index 0000000000..c83d4625dc
--- /dev/null
+++ b/spring-boot-modules/spring-boot-actuator/src/test/java/com/baeldung/metrics/MetricsApplicationIntegrationTest.java
@@ -0,0 +1,64 @@
+package com.baeldung.metrics;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
+
+@SpringBootTest(
+ classes = MetricsApplication.class,
+ webEnvironment = RANDOM_PORT,
+ properties = {"fixedDelay.in.milliseconds=2000"}
+)
+@ActiveProfiles("metrics")
+class MetricsApplicationIntegrationTest {
+
+ @Autowired
+ private TestRestTemplate restTemplate;
+
+ @Test
+ void givenStatuses_WhenScheduledMethodExecuted_ExpectCountsAreAggregated() {
+ restTemplate.getForObject("/metrics/metric/notFound", String.class);
+ restTemplate.getForObject("/metrics/metric", String.class);
+
+ await().untilAsserted(() -> {
+ Object[][] statusCounts = restTemplate.getForObject("/metrics/metric-graph-data", Object[][].class);
+
+ assertThat(statusCounts[0]).contains("counter.status.200", "counter.status.404");
+
+ List requestCounts = getRequestCounts(statusCounts);
+ verify404RequestFrom(requestCounts);
+ verify200RequestsFrom(requestCounts);
+ });
+ }
+
+ private static void verify200RequestsFrom(List requestCounts) {
+ assertThat(requestCounts.size()).isGreaterThan(1);
+ }
+
+ private static void verify404RequestFrom(List requestCounts) {
+ assertThat(requestCounts).contains(1);
+ }
+
+ private static List getRequestCounts(Object[][] statusCounts) {
+ List requestCounts = new ArrayList<>();
+ for (int i = 1; i < statusCounts.length; i++) {
+ for (int j = 1; j < statusCounts[i].length; j++) {
+ Integer count = (Integer) statusCounts[i][j];
+ if (count >= 1) {
+ requestCounts.add(count);
+ }
+ }
+ }
+ return requestCounts;
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-artifacts/pom.xml b/spring-boot-modules/spring-boot-artifacts/pom.xml
index 6bf75491eb..0292dc95cf 100644
--- a/spring-boot-modules/spring-boot-artifacts/pom.xml
+++ b/spring-boot-modules/spring-boot-artifacts/pom.xml
@@ -101,7 +101,7 @@
maven-failsafe-plugin2.18
-
integration-tests
@@ -110,7 +110,7 @@
verify
-
**/ExternalPropertyFileLoaderIntegrationTest.java
@@ -186,7 +186,7 @@
- com.baeldung.boot.Application
+ com.baeldung.wrapper.DemoApplication3.1.13.3.7-12.2
@@ -195,4 +195,4 @@
4.5.8
-
\ No newline at end of file
+
diff --git a/spring-boot-modules/spring-boot-artifacts/src/main/java/com/baeldung/wrapper/DemoApplication.java b/spring-boot-modules/spring-boot-artifacts/src/main/java/com/baeldung/wrapper/DemoApplication.java
new file mode 100644
index 0000000000..d0a7e96bec
--- /dev/null
+++ b/spring-boot-modules/spring-boot-artifacts/src/main/java/com/baeldung/wrapper/DemoApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.wrapper;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication(scanBasePackages = { "com.baeldung" })
+public class DemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(DemoApplication.class, args);
+ }
+}
diff --git a/spring-boot-modules/spring-boot-artifacts/src/main/java/com/baeldung/wrapper/DemoController.java b/spring-boot-modules/spring-boot-artifacts/src/main/java/com/baeldung/wrapper/DemoController.java
new file mode 100644
index 0000000000..530a1d584e
--- /dev/null
+++ b/spring-boot-modules/spring-boot-artifacts/src/main/java/com/baeldung/wrapper/DemoController.java
@@ -0,0 +1,15 @@
+package com.baeldung.wrapper;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@Controller
+public class DemoController {
+
+ @GetMapping(value = "/demo")
+ public String demo(Model model) {
+ return "index";
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-camel/pom.xml b/spring-boot-modules/spring-boot-camel/pom.xml
index 5d5e2ce6bd..5bda1b2351 100644
--- a/spring-boot-modules/spring-boot-camel/pom.xml
+++ b/spring-boot-modules/spring-boot-camel/pom.xml
@@ -16,22 +16,22 @@
- org.apache.camel
+ org.apache.camel.springbootcamel-servlet-starter${camel.version}
- org.apache.camel
+ org.apache.camel.springbootcamel-jackson-starter${camel.version}
- org.apache.camel
+ org.apache.camel.springbootcamel-swagger-java-starter${camel.version}
- org.apache.camel
+ org.apache.camel.springbootcamel-spring-boot-starter${camel.version}
@@ -64,7 +64,8 @@
- 3.0.0-M4
+ 11
+ 3.15.0
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-data-2/README.md b/spring-boot-modules/spring-boot-data-2/README.md
index 875ee502b4..b99356492b 100644
--- a/spring-boot-modules/spring-boot-data-2/README.md
+++ b/spring-boot-modules/spring-boot-data-2/README.md
@@ -1,5 +1,6 @@
### Relevant Articles:
+- [HttpMessageNotWritableException: No Converter for [class …] With Preset Content-Type](https://www.baeldung.com/spring-no-converter-with-preset)
- [Spring Boot: Customize the Jackson ObjectMapper](https://www.baeldung.com/spring-boot-customize-jackson-objectmapper)
- [“HttpMessageNotWritableException: No converter found for return value of type”](https://www.baeldung.com/spring-no-converter-found)
- [Creating a Read-Only Repository with Spring Data](https://www.baeldung.com/spring-data-read-only-repository)
diff --git a/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/controller/StudentRestController.java b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/controller/StudentRestController.java
index 21cb98710d..4119b624e1 100644
--- a/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/controller/StudentRestController.java
+++ b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/controller/StudentRestController.java
@@ -1,5 +1,6 @@
package com.baeldung.boot.noconverterfound.controller;
+import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -18,4 +19,14 @@ public class StudentRestController {
return ResponseEntity.ok(new Student(id, "John", "Wiliams", "AA"));
}
+ @GetMapping(value = "/student/v2/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity getV2(@PathVariable("id") int id) {
+ return ResponseEntity.ok(new Student(id, "Kevin", "Cruyff", "AA"));
+ }
+
+ @GetMapping(value = "/student/v3/{id}", produces = MediaType.APPLICATION_XML_VALUE)
+ public ResponseEntity getV3(@PathVariable("id") int id) {
+ return ResponseEntity.ok(new Student(id, "Robert", "Miller", "BB"));
+ }
+
}
diff --git a/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/noconverterfound/NoConverterFoundIntegrationTest.java b/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/noconverterfound/NoConverterFoundIntegrationTest.java
index f8ded91e65..6f89ef0e58 100644
--- a/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/noconverterfound/NoConverterFoundIntegrationTest.java
+++ b/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/noconverterfound/NoConverterFoundIntegrationTest.java
@@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -25,16 +26,16 @@ public class NoConverterFoundIntegrationTest {
/* Remove Getters from Student class to successfully run this test case
* @Test
public void whenGettersNotDefined_thenThrowException() throws Exception {
-
+
String url = "/api/student/1";
-
+
this.mockMvc.perform(get(url))
.andExpect(status().isInternalServerError())
.andExpect(result -> assertThat(result.getResolvedException())
.isInstanceOf(HttpMessageNotWritableException.class))
.andExpect(result -> assertThat(result.getResolvedException().getMessage())
.contains("No converter found for return value of type"));
-
+
}
*/
@@ -44,9 +45,28 @@ public class NoConverterFoundIntegrationTest {
String url = "/api/student/2";
this.mockMvc.perform(get(url))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.firstName").value("John"));
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.firstName").value("John"));
+ }
+ @Test
+ public void whenJsonConverterIsFound_thenReturnResponse() throws Exception {
+ String url = "/api/student/v2/1";
+
+ this.mockMvc.perform(get(url))
+ .andExpect(status().isOk())
+ .andExpect(content().json("{'id':1,'firstName':'Kevin','lastName':'Cruyff', 'grade':'AA'}"));
+ }
+
+ @Test
+ public void whenConverterNotFound_thenThrowException() throws Exception {
+ String url = "/api/student/v3/1";
+
+ this.mockMvc.perform(get(url))
+ .andExpect(status().isInternalServerError())
+ .andExpect(result -> assertThat(result.getResolvedException()).isInstanceOf(HttpMessageNotWritableException.class))
+ .andExpect(result -> assertThat(result.getResolvedException()
+ .getMessage()).contains("No converter for [class com.baeldung.boot.noconverterfound.model.Student] with preset Content-Type"));
}
}
diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/KeycloakConfig.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/KeycloakConfig.java
new file mode 100644
index 0000000000..6a3dc45717
--- /dev/null
+++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/KeycloakConfig.java
@@ -0,0 +1,14 @@
+package com.baeldung.keycloak;
+
+import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class KeycloakConfig {
+
+ @Bean
+ public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
+ return new KeycloakSpringBootConfigResolver();
+ }
+}
diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/SecurityConfig.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/SecurityConfig.java
index 78023aff8f..826f475a6e 100644
--- a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/SecurityConfig.java
+++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/SecurityConfig.java
@@ -23,11 +23,6 @@ class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
auth.authenticationProvider(keycloakAuthenticationProvider);
}
- @Bean
- public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
- return new KeycloakSpringBootConfigResolver();
- }
-
// Specifies the session authentication strategy
@Bean
@Override
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/README.md b/spring-boot-modules/spring-boot-libraries-comparison/README.md
new file mode 100644
index 0000000000..d373f91b3b
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/README.md
@@ -0,0 +1,7 @@
+## Spring Boot Libraries
+
+This module contains articles about various Spring Boot libraries Comparison
+
+### Relevant Articles:
+
+- [GraphQL vs REST](https://www.baeldung.com/graphql-vs-rest)
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/pom.xml b/spring-boot-modules/spring-boot-libraries-comparison/pom.xml
new file mode 100644
index 0000000000..59d0e75be3
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+ spring-boot-libraries-comparison
+
+
+ com.baeldung.spring-boot-modules
+ spring-boot-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.data
+ spring-data-jpa
+
+
+ com.graphql-java
+ graphql-spring-boot-starter
+ ${graphql-spring-boot-starter.version}
+
+
+ com.graphql-java
+ graphql-java-tools
+ ${graphql-java-tools.version}
+
+
+ com.graphql-java
+ graphiql-spring-boot-starter
+ ${graphql-spring-boot-starter.version}
+
+
+
+
+
+ 5.0.2
+ 5.2.4
+
+
+
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/GraphqlVsRestApplication.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/GraphqlVsRestApplication.java
new file mode 100644
index 0000000000..29a3ef1e0f
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/GraphqlVsRestApplication.java
@@ -0,0 +1,19 @@
+package com.baeldung.graphqlvsrest;
+
+import com.baeldung.graphqlvsrest.configuration.GraphqlConfiguration;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
+import org.springframework.context.annotation.Import;
+
+@SpringBootApplication
+@Import(GraphqlConfiguration.class)
+@EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class})
+public class GraphqlVsRestApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(GraphqlVsRestApplication.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/configuration/GraphqlConfiguration.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/configuration/GraphqlConfiguration.java
new file mode 100644
index 0000000000..c100a03143
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/configuration/GraphqlConfiguration.java
@@ -0,0 +1,35 @@
+package com.baeldung.graphqlvsrest.configuration;
+
+import com.baeldung.graphqlvsrest.repository.OrderRepository;
+import com.baeldung.graphqlvsrest.resolver.Mutation;
+import com.baeldung.graphqlvsrest.resolver.ProductResolver;
+import com.baeldung.graphqlvsrest.resolver.Query;
+import com.baeldung.graphqlvsrest.repository.ProductRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class GraphqlConfiguration {
+
+ @Autowired
+ ProductRepository productRepository;
+
+ @Autowired
+ OrderRepository orderRepository;
+
+ @Bean
+ public Query query() {
+ return new Query(productRepository);
+ }
+
+ @Bean
+ public ProductResolver productResolver(){
+ return new ProductResolver(orderRepository);
+ }
+
+ @Bean
+ public Mutation mutation() {
+ return new Mutation(productRepository);
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/controller/OrderController.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/controller/OrderController.java
new file mode 100644
index 0000000000..14f0468bbd
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/controller/OrderController.java
@@ -0,0 +1,25 @@
+package com.baeldung.graphqlvsrest.controller;
+
+import com.baeldung.graphqlvsrest.entity.Order;
+import com.baeldung.graphqlvsrest.entity.Product;
+import com.baeldung.graphqlvsrest.model.ProductModel;
+import com.baeldung.graphqlvsrest.repository.OrderRepository;
+import com.baeldung.graphqlvsrest.repository.ProductRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("order")
+public class OrderController {
+
+ @Autowired
+ OrderRepository orderRepository;
+
+ @GetMapping()
+ public List getOrders(@RequestParam("product-id") Integer productId){
+ return orderRepository.getOrdersByProduct(productId);
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/controller/ProductController.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/controller/ProductController.java
new file mode 100644
index 0000000000..2fdee8765a
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/controller/ProductController.java
@@ -0,0 +1,38 @@
+package com.baeldung.graphqlvsrest.controller;
+
+import com.baeldung.graphqlvsrest.entity.Product;
+import com.baeldung.graphqlvsrest.model.ProductModel;
+import com.baeldung.graphqlvsrest.repository.ProductRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("product")
+public class ProductController {
+
+ @Autowired
+ ProductRepository productRepository;
+
+ @GetMapping
+ public List getProducts(Pageable pageable){
+ return productRepository.getProducts(pageable.getPageSize(), pageable.getPageNumber());
+ }
+
+ @GetMapping("/{product-id}")
+ public Product getProducts(@PathVariable("product-id") Integer productId){
+ return productRepository.getProduct(productId);
+ }
+
+ @PostMapping
+ public Product save(@RequestBody ProductModel productModel){
+ return productRepository.save(productModel);
+ }
+
+ @PutMapping("/{product-id}")
+ public Product update(@PathVariable("product-id") Integer productId, @RequestBody ProductModel productModel){
+ return productRepository.update(productId, productModel);
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/entity/Order.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/entity/Order.java
new file mode 100644
index 0000000000..89606e9897
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/entity/Order.java
@@ -0,0 +1,58 @@
+package com.baeldung.graphqlvsrest.entity;
+
+public class Order {
+ private Integer id;
+ private Integer product_id;
+ private String customer_uuid;
+ private String status;
+ private String address;
+ private String creation_date;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public Integer getProduct_id() {
+ return product_id;
+ }
+
+ public void setProduct_id(Integer product_id) {
+ this.product_id = product_id;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getCustomer_uuid() {
+ return customer_uuid;
+ }
+
+ public void setCustomer_uuid(String customer_uuid) {
+ this.customer_uuid = customer_uuid;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public String getCreation_date() {
+ return creation_date;
+ }
+
+ public void setCreation_date(String creation_date) {
+ this.creation_date = creation_date;
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/entity/Product.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/entity/Product.java
new file mode 100644
index 0000000000..2da9244c92
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/entity/Product.java
@@ -0,0 +1,115 @@
+package com.baeldung.graphqlvsrest.entity;
+
+import com.baeldung.graphqlvsrest.model.ProductModel;
+
+import java.util.List;
+
+public class Product {
+ private Integer id;
+ private String name;
+ private String description;
+ private String status;
+ private String currency;
+ private Double price;
+ private List image_url;
+ private List video_url;
+ private Integer stock;
+ private Float average_rating;
+
+ public Product(Integer id, ProductModel productModel) {
+ this.id = id;
+ this.name = productModel.getName();
+ this.description = productModel.getDescription();
+ this.currency = productModel.getCurrency();
+ this.price = productModel.getPrice();
+ this.stock = productModel.getStock();
+ this.image_url = productModel.getImage_url();
+ this.video_url = productModel.getVideo_url();
+ this.average_rating = 0F;
+ this.status = productModel.getStatus();
+ }
+
+ public Product(){
+
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getCurrency() {
+ return currency;
+ }
+
+ public void setCurrency(String currency) {
+ this.currency = currency;
+ }
+
+ public Double getPrice() {
+ return price;
+ }
+
+ public void setPrice(Double price) {
+ this.price = price;
+ }
+
+ public List getImage_url() {
+ return image_url;
+ }
+
+ public void setImage_url(List image_url) {
+ this.image_url = image_url;
+ }
+
+ public List getVideo_url() {
+ return video_url;
+ }
+
+ public void setVideo_url(List video_url) {
+ this.video_url = video_url;
+ }
+
+ public Integer getStock() {
+ return stock;
+ }
+
+ public void setStock(Integer stock) {
+ this.stock = stock;
+ }
+
+ public Float getAverage_rating() {
+ return average_rating;
+ }
+
+ public void setAverage_rating(Float average_rating) {
+ this.average_rating = average_rating;
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/model/ProductModel.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/model/ProductModel.java
new file mode 100644
index 0000000000..db7a3ba54e
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/model/ProductModel.java
@@ -0,0 +1,92 @@
+package com.baeldung.graphqlvsrest.model;
+
+import java.util.List;
+
+public class ProductModel {
+ private String name;
+ private String description;
+ private String status;
+ private String currency;
+ private Double price;
+ private List image_url;
+ private List video_url;
+ private Integer stock;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getCurrency() {
+ return currency;
+ }
+
+ public void setCurrency(String currency) {
+ this.currency = currency;
+ }
+
+ public Double getPrice() {
+ return price;
+ }
+
+ public void setPrice(Double price) {
+ this.price = price;
+ }
+
+ public List getImage_url() {
+ return image_url;
+ }
+
+ public void setImage_url(List image_url) {
+ this.image_url = image_url;
+ }
+
+ public List getVideo_url() {
+ return video_url;
+ }
+
+ public void setVideo_url(List video_url) {
+ this.video_url = video_url;
+ }
+
+ public Integer getStock() {
+ return stock;
+ }
+
+ public void setStock(Integer stock) {
+ this.stock = stock;
+ }
+
+ @Override
+ public String toString() {
+ return "ProductModel{" +
+ "name='" + name + '\'' +
+ ", description='" + description + '\'' +
+ ", status='" + status + '\'' +
+ ", currency='" + currency + '\'' +
+ ", price=" + price +
+ ", image_url=" + image_url +
+ ", video_url=" + video_url +
+ ", stock=" + stock +
+ '}';
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/repository/OrderRepository.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/repository/OrderRepository.java
new file mode 100644
index 0000000000..92cc288426
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/repository/OrderRepository.java
@@ -0,0 +1,11 @@
+package com.baeldung.graphqlvsrest.repository;
+
+import com.baeldung.graphqlvsrest.entity.Order;
+import com.baeldung.graphqlvsrest.entity.Product;
+import com.baeldung.graphqlvsrest.model.ProductModel;
+
+import java.util.List;
+
+public interface OrderRepository {
+ List getOrdersByProduct(Integer productId);
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/repository/ProductRepository.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/repository/ProductRepository.java
new file mode 100644
index 0000000000..c0fb12327f
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/repository/ProductRepository.java
@@ -0,0 +1,14 @@
+package com.baeldung.graphqlvsrest.repository;
+
+import com.baeldung.graphqlvsrest.entity.Product;
+import com.baeldung.graphqlvsrest.model.ProductModel;
+
+import java.util.List;
+
+public interface ProductRepository {
+ List getProducts(Integer pageSize, Integer pageNumber);
+ Product getProduct(Integer id);
+ Product save(ProductModel productModel);
+ Product update(Integer productId, ProductModel productModel);
+
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/repository/impl/OrderRepositoryImpl.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/repository/impl/OrderRepositoryImpl.java
new file mode 100644
index 0000000000..e4f316c865
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/repository/impl/OrderRepositoryImpl.java
@@ -0,0 +1,36 @@
+package com.baeldung.graphqlvsrest.repository.impl;
+
+import com.baeldung.graphqlvsrest.entity.Order;
+import com.baeldung.graphqlvsrest.repository.OrderRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Repository
+public class OrderRepositoryImpl implements OrderRepository {
+
+ private static List orderList = new ArrayList<>();
+
+ public OrderRepositoryImpl() {
+ for (int i = 1; i <= 100; i++){
+ Order order = new Order();
+ order.setId(i);
+ order.setProduct_id(i%10);
+ order.setAddress(UUID.randomUUID().toString());
+ order.setCustomer_uuid(UUID.randomUUID().toString());
+ order.setCreation_date(new Date(System.currentTimeMillis()).toString());
+ order.setStatus("Delivered");
+ orderList.add(order);
+ }
+ }
+
+
+ @Override
+ public List getOrdersByProduct(Integer productId) {
+ return orderList.stream().filter(order -> order.getProduct_id().equals(productId)).collect(Collectors.toList());
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/repository/impl/ProductRepositoryImpl.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/repository/impl/ProductRepositoryImpl.java
new file mode 100644
index 0000000000..845472faea
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/repository/impl/ProductRepositoryImpl.java
@@ -0,0 +1,74 @@
+package com.baeldung.graphqlvsrest.repository.impl;
+
+import com.baeldung.graphqlvsrest.entity.Product;
+import com.baeldung.graphqlvsrest.model.ProductModel;
+import com.baeldung.graphqlvsrest.repository.ProductRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Repository
+public class ProductRepositoryImpl implements ProductRepository {
+
+ private static List productList = new ArrayList<>();
+
+ public ProductRepositoryImpl() {
+ for (int i = 1; i <= 10; i++){
+ Product product = new Product();
+ product.setId(i);
+ product.setName(String.format("Product %d", i));
+ product.setDescription(String.format("Product %d description", i));
+ product.setCurrency(String.format("Product %d currency", i));
+ product.setPrice(Double.valueOf(i^2));
+ product.setStock(10);
+ product.setAverage_rating(0F);
+ product.setImage_url(Arrays.asList(String.format("www.baeldung.com/imageurl/%d", i)));
+ product.setVideo_url(Arrays.asList(String.format("www.baeldung.com/videourl/%d", i)));
+ productList.add(product);
+ }
+ }
+
+ @Override
+ public List getProducts(Integer pageSize, Integer pageNumber) {
+ return productList.stream().skip(pageSize*pageNumber).limit(pageSize).collect(Collectors.toList());
+ }
+
+ @Override
+ public Product getProduct(Integer id) {
+ return productList.stream().filter(product -> product.getId().equals(id)).findFirst().orElse(null);
+ }
+
+ @Override
+ public Product save(ProductModel productModel) {
+ Product product = new Product(productList.size()+1, productModel);
+ productList.add(product);
+ return product;
+ }
+
+ @Override
+ public Product update(Integer productId, ProductModel productModel) {
+ Product product = getProduct(productId);
+ if (product != null){
+ update(product, productModel);
+ }
+ return product;
+ }
+
+ private void update(Product product, ProductModel productModel){
+ if (productModel != null) {
+ System.out.println(productModel.toString());
+ Optional.ofNullable(productModel.getName()).ifPresent(product::setName);
+ Optional.ofNullable(productModel.getDescription()).ifPresent(product::setDescription);
+ Optional.ofNullable(productModel.getCurrency()).ifPresent(product::setCurrency);
+ Optional.ofNullable(productModel.getImage_url()).ifPresent(product::setImage_url);
+ Optional.ofNullable(productModel.getStock()).ifPresent(product::setStock);
+ Optional.ofNullable(productModel.getStatus()).ifPresent(product::setStatus);
+ Optional.ofNullable(productModel.getVideo_url()).ifPresent(product::setVideo_url);
+ Optional.ofNullable(productModel.getPrice()).ifPresent(product::setPrice);
+ }
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/resolver/Mutation.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/resolver/Mutation.java
new file mode 100644
index 0000000000..3d643f97e6
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/resolver/Mutation.java
@@ -0,0 +1,22 @@
+package com.baeldung.graphqlvsrest.resolver;
+
+import com.baeldung.graphqlvsrest.entity.Product;
+import com.baeldung.graphqlvsrest.model.ProductModel;
+import com.baeldung.graphqlvsrest.repository.ProductRepository;
+import com.coxautodev.graphql.tools.GraphQLMutationResolver;
+
+public class Mutation implements GraphQLMutationResolver {
+
+ private ProductRepository productRepository;
+ public Mutation(ProductRepository productRepository){
+ this.productRepository = productRepository;
+ }
+
+ public Product saveProduct(ProductModel productModel) {
+ return productRepository.save(productModel);
+ }
+
+ public Product updateProduct(Integer productId, ProductModel productModel) {
+ return productRepository.update(productId, productModel);
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/resolver/ProductResolver.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/resolver/ProductResolver.java
new file mode 100644
index 0000000000..f20b8d5920
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/resolver/ProductResolver.java
@@ -0,0 +1,18 @@
+package com.baeldung.graphqlvsrest.resolver;
+
+import com.baeldung.graphqlvsrest.entity.Order;
+import com.baeldung.graphqlvsrest.entity.Product;
+import com.baeldung.graphqlvsrest.repository.OrderRepository;
+import com.coxautodev.graphql.tools.GraphQLResolver;
+
+import java.util.List;
+
+public class ProductResolver implements GraphQLResolver {
+ private OrderRepository orderRepository;
+ public ProductResolver(OrderRepository orderRepository){
+ this.orderRepository = orderRepository;
+ }
+ public List getOrders(Product product){
+ return orderRepository.getOrdersByProduct(product.getId());
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/resolver/Query.java b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/resolver/Query.java
new file mode 100644
index 0000000000..0d218261b2
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/java/com/baeldung/graphqlvsrest/resolver/Query.java
@@ -0,0 +1,27 @@
+package com.baeldung.graphqlvsrest.resolver;
+
+import com.baeldung.graphqlvsrest.entity.Order;
+import com.baeldung.graphqlvsrest.entity.Product;
+import com.baeldung.graphqlvsrest.repository.OrderRepository;
+import com.baeldung.graphqlvsrest.repository.ProductRepository;
+import com.coxautodev.graphql.tools.GraphQLQueryResolver;
+
+import java.util.List;
+
+public class Query implements GraphQLQueryResolver {
+
+ private ProductRepository productRepository;
+ public Query(ProductRepository productRepository){
+ this.productRepository = productRepository;
+ }
+
+ public List getProducts(int pageSize, int pageNumber) {
+ return productRepository.getProducts(pageSize, pageNumber);
+ }
+
+ public Product getProduct(int id) {
+ return productRepository.getProduct(id);
+ }
+
+
+}
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/resources/application.properties b/spring-boot-modules/spring-boot-libraries-comparison/src/main/resources/application.properties
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/spring-boot-modules/spring-boot-libraries-comparison/src/main/resources/graphql/schema.graphqls b/spring-boot-modules/spring-boot-libraries-comparison/src/main/resources/graphql/schema.graphqls
new file mode 100644
index 0000000000..2709510d72
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-comparison/src/main/resources/graphql/schema.graphqls
@@ -0,0 +1,57 @@
+type Product {
+ id: ID
+ name: String!
+ description: String
+ status: String
+ currency: String!
+ price: Float
+ image_url: [String]
+ video_url: [String]
+ stock: Int
+ average_rating: Float
+ orders:[Order]
+}
+
+type Order{
+ id:ID
+ product_id:Int
+ customer_uuid:String
+ address:String
+ status:String
+ creation_date:String
+}
+
+input ProductModel {
+ name: String!
+ description: String
+ status: String
+ currency: String!
+ price: Float
+ image_url: [String]
+ video_url: [String]
+ stock: Int
+}
+
+input ProductUpdateModel {
+ name: String
+ description: String
+ status: String
+ currency: String
+ price: Float
+ image_url: [String]
+ video_url: [String]
+ stock: Int
+}
+
+
+# The Root Query for the application
+type Query {
+ products(size: Int, page: Int): [Product]!
+ product(id: Int): Product!
+}
+
+# The Root Mutation for the application
+type Mutation {
+ saveProduct(product: ProductModel) : Product!
+ updateProduct(id: Int, product: ProductUpdateModel) : Product!
+}
diff --git a/spring-boot-modules/spring-boot-logging-log4j2/pom.xml b/spring-boot-modules/spring-boot-logging-log4j2/pom.xml
index 8cf052deb3..036df19887 100644
--- a/spring-boot-modules/spring-boot-logging-log4j2/pom.xml
+++ b/spring-boot-modules/spring-boot-logging-log4j2/pom.xml
@@ -102,6 +102,7 @@
4.13.25.8.1
+ 2.17.1
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/.gitignore b/spring-boot-modules/spring-boot-multiple-datasources/.gitignore
new file mode 100644
index 0000000000..87a3fce287
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/.gitignore
@@ -0,0 +1 @@
+.local-db
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/pom.xml b/spring-boot-modules/spring-boot-multiple-datasources/pom.xml
new file mode 100644
index 0000000000..9355de8a36
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/pom.xml
@@ -0,0 +1,58 @@
+
+
+ 4.0.0
+ spring-boot-multiple-datasources
+ 0.1.0-SNAPSHOT
+ spring-boot-multiple-datasources
+ jar
+ Module For Spring Boot With Multiple Datasources
+
+
+ com.baeldung.spring-boot-modules
+ spring-boot-modules
+ 1.0.0-SNAPSHOT
+ ../pom.xml
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ com.h2database
+ h2
+ provided
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+ 2.6.3
+
+
+
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/MultipleDatasourcesApplication.java b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/MultipleDatasourcesApplication.java
new file mode 100644
index 0000000000..efdff387df
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/MultipleDatasourcesApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.spring.datasources;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class MultipleDatasourcesApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(MultipleDatasourcesApplication.class, args);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/todos/Todo.java b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/todos/Todo.java
new file mode 100644
index 0000000000..6f8557e258
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/todos/Todo.java
@@ -0,0 +1,48 @@
+package com.baeldung.spring.datasources.todos;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class Todo {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ private String title;
+ private boolean completed;
+
+ public Todo() {
+ }
+
+ public Todo(String title) {
+ this.title = title;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public boolean isCompleted() {
+ return completed;
+ }
+
+ public void setCompleted(boolean completed) {
+ this.completed = completed;
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/todos/TodoDatasourceConfiguration.java b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/todos/TodoDatasourceConfiguration.java
new file mode 100644
index 0000000000..3bfc8e2855
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/todos/TodoDatasourceConfiguration.java
@@ -0,0 +1,28 @@
+package com.baeldung.spring.datasources.todos;
+
+import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+import javax.sql.DataSource;
+
+@Configuration
+public class TodoDatasourceConfiguration {
+
+ @Bean
+ @ConfigurationProperties("spring.datasource.todos")
+ public DataSourceProperties todosDataSourceProperties() {
+ return new DataSourceProperties();
+ }
+
+ @Bean
+ @Primary
+ public DataSource todosDataSource() {
+ return todosDataSourceProperties()
+ .initializeDataSourceBuilder()
+ .build();
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/todos/TodoJpaConfiguration.java b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/todos/TodoJpaConfiguration.java
new file mode 100644
index 0000000000..655a3a55c2
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/todos/TodoJpaConfiguration.java
@@ -0,0 +1,40 @@
+package com.baeldung.spring.datasources.todos;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+import java.util.Objects;
+
+@Configuration
+@EnableTransactionManagement
+@EnableJpaRepositories(
+ basePackageClasses = Todo.class,
+ entityManagerFactoryRef = "todosEntityManagerFactory",
+ transactionManagerRef = "todosTransactionManager"
+)
+public class TodoJpaConfiguration {
+
+ @Bean
+ public LocalContainerEntityManagerFactoryBean todosEntityManagerFactory(
+ @Qualifier("todosDataSource") DataSource dataSource,
+ EntityManagerFactoryBuilder builder) {
+ return builder
+ .dataSource(dataSource)
+ .packages(Todo.class)
+ .build();
+ }
+
+ @Bean
+ public PlatformTransactionManager todosTransactionManager(
+ @Qualifier("todosEntityManagerFactory") LocalContainerEntityManagerFactoryBean todosEntityManagerFactory) {
+ return new JpaTransactionManager(Objects.requireNonNull(todosEntityManagerFactory.getObject()));
+ }
+}
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/todos/TodoRepository.java b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/todos/TodoRepository.java
new file mode 100644
index 0000000000..09fb8c6500
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/todos/TodoRepository.java
@@ -0,0 +1,6 @@
+package com.baeldung.spring.datasources.todos;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TodoRepository extends JpaRepository {
+}
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/topics/Topic.java b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/topics/Topic.java
new file mode 100644
index 0000000000..1d1f20f111
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/topics/Topic.java
@@ -0,0 +1,39 @@
+package com.baeldung.spring.datasources.topics;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class Topic {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ private String title;
+
+ public Topic() {
+ }
+
+ public Topic(String title) {
+ this.title = title;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/topics/TopicDatasourceConfiguration.java b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/topics/TopicDatasourceConfiguration.java
new file mode 100644
index 0000000000..a06983d681
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/topics/TopicDatasourceConfiguration.java
@@ -0,0 +1,26 @@
+package com.baeldung.spring.datasources.topics;
+
+import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+
+@Configuration
+public class TopicDatasourceConfiguration {
+
+ @Bean
+ @ConfigurationProperties("spring.datasource.topics")
+ public DataSourceProperties topicsDataSourceProperties() {
+ return new DataSourceProperties();
+ }
+
+ @Bean
+ public DataSource topicsDataSource() {
+ return topicsDataSourceProperties()
+ .initializeDataSourceBuilder()
+ .build();
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/topics/TopicJpaConfiguration.java b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/topics/TopicJpaConfiguration.java
new file mode 100644
index 0000000000..d800813b8c
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/topics/TopicJpaConfiguration.java
@@ -0,0 +1,41 @@
+package com.baeldung.spring.datasources.topics;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+import java.util.Objects;
+
+@Configuration
+@EnableTransactionManagement
+@EnableJpaRepositories(
+ basePackageClasses = Topic.class,
+ entityManagerFactoryRef = "topicsEntityManagerFactory",
+ transactionManagerRef = "topicsTransactionManager"
+)
+public class TopicJpaConfiguration {
+
+ @Bean
+ public LocalContainerEntityManagerFactoryBean topicsEntityManagerFactory(
+ @Qualifier("topicsDataSource") DataSource dataSource,
+ EntityManagerFactoryBuilder builder
+ ) {
+ return builder
+ .dataSource(dataSource)
+ .packages(Topic.class)
+ .build();
+ }
+
+ @Bean
+ public PlatformTransactionManager topicsTransactionManager(
+ @Qualifier("topicsEntityManagerFactory") LocalContainerEntityManagerFactoryBean topicsEntityManagerFactory) {
+ return new JpaTransactionManager(Objects.requireNonNull(topicsEntityManagerFactory.getObject()));
+ }
+}
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/topics/TopicRepository.java b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/topics/TopicRepository.java
new file mode 100644
index 0000000000..499b72650a
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/src/main/java/com/baeldung/spring/datasources/topics/TopicRepository.java
@@ -0,0 +1,6 @@
+package com.baeldung.spring.datasources.topics;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TopicRepository extends JpaRepository {
+}
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/src/main/resources/application.yml b/spring-boot-modules/spring-boot-multiple-datasources/src/main/resources/application.yml
new file mode 100644
index 0000000000..4754acfc0f
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/src/main/resources/application.yml
@@ -0,0 +1,23 @@
+spring:
+ datasource:
+ todos:
+ url: jdbc:h2:./.local-db/todos;DB_CLOSE_DELAY=-1;MODE=DB2;AUTO_SERVER=TRUE
+ username: sa
+ password: null
+ driverClassName: org.h2.Driver
+ topics:
+ url: jdbc:h2:./.local-db/topics;DB_CLOSE_DELAY=-1;MODE=DB2;AUTO_SERVER=TRUE
+ username: sa
+ password: null
+ driverClassName: org.h2.Driver
+ h2:
+ console:
+ enabled: true
+ path: /h2-console
+ jpa:
+ generate-ddl: true
+ hibernate:
+ ddl-auto: update
+ properties:
+ hibernate:
+ dialect: org.hibernate.dialect.H2Dialect
diff --git a/spring-boot-modules/spring-boot-multiple-datasources/src/test/java/com/baeldung/spring/datasources/MultipleDatasourcesIntegrationTest.java b/spring-boot-modules/spring-boot-multiple-datasources/src/test/java/com/baeldung/spring/datasources/MultipleDatasourcesIntegrationTest.java
new file mode 100644
index 0000000000..397992f6c8
--- /dev/null
+++ b/spring-boot-modules/spring-boot-multiple-datasources/src/test/java/com/baeldung/spring/datasources/MultipleDatasourcesIntegrationTest.java
@@ -0,0 +1,39 @@
+package com.baeldung.spring.datasources;
+
+import com.baeldung.spring.datasources.todos.Todo;
+import com.baeldung.spring.datasources.todos.TodoRepository;
+import com.baeldung.spring.datasources.topics.Topic;
+import com.baeldung.spring.datasources.topics.TopicRepository;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DataJpaTest // no test database!
+class MultipleDatasourcesIntegrationTest {
+
+ @Autowired
+ TodoRepository todoRepo;
+ @Autowired
+ TopicRepository topicRepo;
+
+ @Test
+ void shouldSaveTodoToTodoDB() {
+ Todo todo = new Todo("test");
+ Todo saved =todoRepo.save(todo);
+ Optional result= todoRepo.findById(saved.getId());
+ assertThat(result).isPresent();
+ }
+
+ @Test
+ void shouldSaveTopicToTopicDB() {
+ Topic todo = new Topic("test");
+ Topic saved =topicRepo.save(todo);
+ Optional result= topicRepo.findById(saved.getId());
+ assertThat(result).isPresent();
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-mvc-birt/pom.xml b/spring-boot-modules/spring-boot-mvc-birt/pom.xml
index 16b07000f8..8f1c770bbe 100644
--- a/spring-boot-modules/spring-boot-mvc-birt/pom.xml
+++ b/spring-boot-modules/spring-boot-mvc-birt/pom.xml
@@ -70,6 +70,7 @@
1.84.8.01.2.17
+ 2.17.1
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-react/pom.xml b/spring-boot-modules/spring-boot-react/pom.xml
index d515aed6ce..dc0c6914c9 100644
--- a/spring-boot-modules/spring-boot-react/pom.xml
+++ b/spring-boot-modules/spring-boot-react/pom.xml
@@ -11,6 +11,18 @@
1.0.0-SNAPSHOT
+
+
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
+
+
+
org.springframework.boot
@@ -128,6 +140,7 @@
v1.12.12.4.41.0.2
+ 2.17.1
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-security/README.md b/spring-boot-modules/spring-boot-security/README.md
index b9966709cb..d848f798ba 100644
--- a/spring-boot-modules/spring-boot-security/README.md
+++ b/spring-boot-modules/spring-boot-security/README.md
@@ -10,6 +10,7 @@ This module contains articles about Spring Boot Security
- [Guide to @CurrentSecurityContext in Spring Security](https://www.baeldung.com/spring-currentsecuritycontext)
- [Disable Security for a Profile in Spring Boot](https://www.baeldung.com/spring-security-disable-profile)
- [Spring @EnableWebSecurity vs. @EnableGlobalMethodSecurity](https://www.baeldung.com/spring-enablewebsecurity-vs-enableglobalmethodsecurity)
+- [Spring Security – Configuring Different URLs](https://www.baeldung.com/spring-security-configuring-urls)
### Spring Boot Security Auto-Configuration
diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/AntMatchersExampleApplication.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/AntMatchersExampleApplication.java
new file mode 100644
index 0000000000..0a7828eff4
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/AntMatchersExampleApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.antmatchers;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class AntMatchersExampleApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(AntMatchersExampleApplication.class, args);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/config/SecurityConfiguration.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/config/SecurityConfiguration.java
new file mode 100644
index 0000000000..43e8c73bf0
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/config/SecurityConfiguration.java
@@ -0,0 +1,39 @@
+package com.baeldung.antmatchers.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ @Override
+ public void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.inMemoryAuthentication()
+ .withUser("admin").password(passwordEncoder().encode("password")).roles("USER", "ADMIN")
+ .and()
+ .withUser("user").password(passwordEncoder().encode("password")).roles("USER");
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .authorizeRequests()
+ .antMatchers("/products/**").permitAll()
+ .and()
+ .authorizeRequests()
+ .antMatchers("/customers/**").hasRole("ADMIN")
+ .anyRequest().authenticated()
+ .and()
+ .httpBasic();
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/controllers/CustomerController.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/controllers/CustomerController.java
new file mode 100644
index 0000000000..a031449797
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/controllers/CustomerController.java
@@ -0,0 +1,15 @@
+package com.baeldung.antmatchers.controllers;
+
+import com.baeldung.antmatchers.dtos.Customer;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class CustomerController {
+
+ @GetMapping("/customers/{id}")
+ public Customer getCustomerById(@PathVariable("id") String id) {
+ return new Customer("Customer 1", "Address 1", "Phone 1");
+ }
+}
diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/controllers/ProductController.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/controllers/ProductController.java
new file mode 100644
index 0000000000..2968f3c025
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/controllers/ProductController.java
@@ -0,0 +1,21 @@
+package com.baeldung.antmatchers.controllers;
+
+import com.baeldung.antmatchers.dtos.Product;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RestController
+public class ProductController {
+
+ @GetMapping("/products")
+ public List getProducts() {
+ return new ArrayList<>(Arrays.asList(
+ new Product("Product 1", "Description 1", 1.0),
+ new Product("Product 2", "Description 2", 2.0)
+ ));
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/dtos/Customer.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/dtos/Customer.java
new file mode 100644
index 0000000000..64bd95b095
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/dtos/Customer.java
@@ -0,0 +1,39 @@
+package com.baeldung.antmatchers.dtos;
+
+import java.io.Serializable;
+
+public class Customer implements Serializable {
+ private String name;
+ private String address;
+ private String phone;
+
+ public Customer(String name, String address, String phone) {
+ this.name = name;
+ this.address = address;
+ this.phone = phone;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public String getPhone() {
+ return phone;
+ }
+
+ public void setPhone(String phone) {
+ this.phone = phone;
+ }
+}
diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/dtos/Product.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/dtos/Product.java
new file mode 100644
index 0000000000..84073ee8c0
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/antmatchers/dtos/Product.java
@@ -0,0 +1,39 @@
+package com.baeldung.antmatchers.dtos;
+
+import java.io.Serializable;
+
+public class Product implements Serializable {
+ private String name;
+ private String description;
+ private double price;
+
+ public Product(String name, String description, double price) {
+ this.name = name;
+ this.description = description;
+ this.price = price;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public double getPrice() {
+ return price;
+ }
+
+ public void setPrice(double price) {
+ this.price = price;
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/antmatchers/controllers/CustomerControllerTest.java b/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/antmatchers/controllers/CustomerControllerTest.java
new file mode 100644
index 0000000000..de13dc0aea
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/antmatchers/controllers/CustomerControllerTest.java
@@ -0,0 +1,38 @@
+package com.baeldung.antmatchers.controllers;
+
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@RunWith(SpringRunner.class)
+@WebMvcTest(value = CustomerController.class)
+class CustomerControllerIntegrationTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Test
+ void getCustomerByIdUnauthorized() throws Exception {
+ mockMvc.perform(get("/customers/1")).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ void getCustomerByIdForbidden() throws Exception {
+ mockMvc.perform(get("/customers/1").with(user("user").roles("USER")))
+ .andExpect(status().isForbidden());
+ }
+
+ @Test
+ void getCustomerByIdOk() throws Exception {
+ mockMvc.perform(get("/customers/1").with(user("admin").roles("ADMIN")))
+ .andExpect(status().isOk());
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/antmatchers/controllers/ProductControllerTest.java b/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/antmatchers/controllers/ProductControllerTest.java
new file mode 100644
index 0000000000..370e5f1597
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/antmatchers/controllers/ProductControllerTest.java
@@ -0,0 +1,25 @@
+package com.baeldung.antmatchers.controllers;
+
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@RunWith(SpringRunner.class)
+@WebMvcTest(value = ProductController.class)
+class ProductControllerIntegrationTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Test
+ void getProducts() throws Exception {
+ mockMvc.perform(get("/products"))
+ .andExpect(status().isOk());
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/swaggerresponseapi/SwaggerResponseApiApplication.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/swaggerresponseapi/SwaggerResponseApiApplication.java
new file mode 100644
index 0000000000..913544b459
--- /dev/null
+++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/swaggerresponseapi/SwaggerResponseApiApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.swaggerresponseapi;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SwaggerResponseApiApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SwaggerResponseApiApplication.class, args);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/swaggerresponseapi/controller/ProductController.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/swaggerresponseapi/controller/ProductController.java
new file mode 100644
index 0000000000..364a7e8a66
--- /dev/null
+++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/swaggerresponseapi/controller/ProductController.java
@@ -0,0 +1,38 @@
+package com.baeldung.swaggerresponseapi.controller;
+
+import com.baeldung.swaggerresponseapi.model.Product;
+import com.baeldung.swaggerresponseapi.service.ProductService;
+
+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.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+public class ProductController {
+ private final ProductService productService;
+
+ public ProductController(ProductService productService) {
+ this.productService = productService;
+ }
+
+ @PostMapping("/create")
+ public Product addProduct(@RequestBody Product product) {
+ return productService.addProducts(product);
+ }
+
+ @ApiResponses(value = { @ApiResponse(content = { @Content(mediaType = "application/json",
+ array = @ArraySchema(schema = @Schema(implementation = Product.class))) }) })
+ @GetMapping("/products")
+ public List getProductsList() {
+ return productService.getProductsList();
+ }
+}
diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/swaggerresponseapi/model/Product.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/swaggerresponseapi/model/Product.java
new file mode 100644
index 0000000000..036ecbc853
--- /dev/null
+++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/swaggerresponseapi/model/Product.java
@@ -0,0 +1,28 @@
+package com.baeldung.swaggerresponseapi.model;
+
+public class Product {
+ String code;
+ String name;
+
+ public Product(String code, String name) {
+ this.code = code;
+ this.name = name;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
+
diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/swaggerresponseapi/service/ProductService.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/swaggerresponseapi/service/ProductService.java
new file mode 100644
index 0000000000..5e7533d6f4
--- /dev/null
+++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/swaggerresponseapi/service/ProductService.java
@@ -0,0 +1,21 @@
+package com.baeldung.swaggerresponseapi.service;
+
+import com.baeldung.swaggerresponseapi.model.Product;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class ProductService {
+ List productsList = new ArrayList<>();
+
+ public Product addProducts(Product product) {
+ productsList.add(product);
+ return product;
+ }
+
+ public List getProductsList() {
+ return productsList;
+ }
+}
diff --git a/spring-boot-modules/spring-boot-swagger-keycloak/pom.xml b/spring-boot-modules/spring-boot-swagger-keycloak/pom.xml
index 82e1951b8e..de2c8c68c4 100644
--- a/spring-boot-modules/spring-boot-swagger-keycloak/pom.xml
+++ b/spring-boot-modules/spring-boot-swagger-keycloak/pom.xml
@@ -24,6 +24,13 @@
pomimport
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
@@ -62,6 +69,7 @@
2.4.53.0.015.0.2
+ 2.17.1
-
+
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-swagger/README.md b/spring-boot-modules/spring-boot-swagger/README.md
index ef33be8e07..a09df23a80 100644
--- a/spring-boot-modules/spring-boot-swagger/README.md
+++ b/spring-boot-modules/spring-boot-swagger/README.md
@@ -3,3 +3,6 @@
- [Hiding Endpoints From Swagger Documentation in Spring Boot](https://www.baeldung.com/spring-swagger-hiding-endpoints)
- [Swagger @Api Description Is Deprecated](https://www.baeldung.com/java-swagger-api-description-deprecated)
- [Generate PDF from Swagger API Documentation](https://www.baeldung.com/swagger-generate-pdf)
+- [Remove Basic Error Controller In SpringFox Swagger-UI](https://www.baeldung.com/spring-swagger-remove-error-controller)
+- [Setting Example and Description with Swagger](https://www.baeldung.com/swagger-set-example-description)
+- [Document Enum in Swagger](https://www.baeldung.com/swagger-enum)
diff --git a/spring-boot-modules/spring-boot-swagger/pom.xml b/spring-boot-modules/spring-boot-swagger/pom.xml
index d6b62bce3c..749442c225 100644
--- a/spring-boot-modules/spring-boot-swagger/pom.xml
+++ b/spring-boot-modules/spring-boot-swagger/pom.xml
@@ -25,6 +25,11 @@
springfox-boot-starter${springfox.version}
+
+ com.github.kongchen
+ swagger-maven-plugin
+ ${swagger-maven-plugin.version}
+
@@ -33,11 +38,50 @@
org.springframework.bootspring-boot-maven-plugin
+
+ com.github.kongchen
+ swagger-maven-plugin
+ ${swagger-maven-plugin.version}
+
+
+
+ false
+ com.baeldung.swaggerenums.controller
+ http,https
+ baeldung.com
+ /api
+
+ Baeldung - Document Enum
+ v1
+ This is a Baeldung Document Enum Sample Code
+
+ pmurria@baeldung.com
+ Parikshit Murria
+
+
+ https://www.apache.org/licenses/LICENSE-2.0.html
+ Apache 2.0
+
+
+ ${basedir}/target/swagger-ui
+
+
+
+
+
+ compile
+
+ generate
+
+
+
+ 3.0.0
+ 3.1.1
-
+
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/SpringBootSwaggerConfApplication.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/SpringBootSwaggerConfApplication.java
new file mode 100644
index 0000000000..1d2e129f26
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/SpringBootSwaggerConfApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.swaggerconf;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringBootSwaggerConfApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBootSwaggerConfApplication.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/configuration/SwaggerConfiguration.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/configuration/SwaggerConfiguration.java
new file mode 100644
index 0000000000..a78335a2fe
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/configuration/SwaggerConfiguration.java
@@ -0,0 +1,37 @@
+package com.baeldung.swaggerconf.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.bind.annotation.RestController;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+import java.util.Collections;
+
+import static springfox.documentation.builders.PathSelectors.regex;
+
+@Configuration
+@EnableSwagger2
+public class SwaggerConfiguration {
+
+ private ApiInfo apiInfo() {
+ return new ApiInfo("My REST API", "Some custom description of API.", "API TOS", "Terms of service",
+ new Contact("General UserName", "www.baeldung.com", "user-name@gmail.com"),
+ "License of API", "API license URL", Collections.emptyList());
+ }
+
+ @Bean
+ public Docket api() {
+ return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
+ .select()
+ .apis(RequestHandlerSelectors.basePackage("com.baeldung.swaggerconf.controller"))
+ .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
+ .paths(regex("/good-path/.*"))
+ .build();
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/controller/ErrorControllerExcludedForAnnotation.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/controller/ErrorControllerExcludedForAnnotation.java
new file mode 100644
index 0000000000..65059bf446
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/controller/ErrorControllerExcludedForAnnotation.java
@@ -0,0 +1,17 @@
+package com.baeldung.swaggerconf.controller;
+
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Component
+@RequestMapping("good-path/error-excluded-annotation")
+public class ErrorControllerExcludedForAnnotation extends BasicErrorController {
+
+ public ErrorControllerExcludedForAnnotation(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
+ super(errorAttributes, serverProperties.getError());
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/controller/ErrorControllerExcludedForApiIgnore.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/controller/ErrorControllerExcludedForApiIgnore.java
new file mode 100644
index 0000000000..88208d8446
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/controller/ErrorControllerExcludedForApiIgnore.java
@@ -0,0 +1,21 @@
+package com.baeldung.swaggerconf.controller;
+
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import springfox.documentation.annotations.ApiIgnore;
+
+@Component
+@RequestMapping("good-path/error-excluded-apiignore")
+@RestController
+@ApiIgnore
+public class ErrorControllerExcludedForApiIgnore extends BasicErrorController {
+
+ public ErrorControllerExcludedForApiIgnore(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
+ super(errorAttributes, serverProperties.getError());
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/controller/ErrorControllerExcludedForPath.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/controller/ErrorControllerExcludedForPath.java
new file mode 100644
index 0000000000..5cea4f5ef9
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/controller/ErrorControllerExcludedForPath.java
@@ -0,0 +1,19 @@
+package com.baeldung.swaggerconf.controller;
+
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Component
+@RequestMapping("wrong-path/error-excluded-path")
+@RestController
+public class ErrorControllerExcludedForPath extends BasicErrorController {
+
+ public ErrorControllerExcludedForPath(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
+ super(errorAttributes, serverProperties.getError());
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/controller/RegularRestController.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/controller/RegularRestController.java
new file mode 100644
index 0000000000..163826e626
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/controller/RegularRestController.java
@@ -0,0 +1,33 @@
+package com.baeldung.swaggerconf.controller;
+
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+
+@RestController
+@RequestMapping("good-path")
+public class RegularRestController {
+
+ @ApiOperation(value = "This method is used to get the author name.")
+ @GetMapping("/getAuthor")
+ public String getAuthor() {
+ return "Name Surname";
+ }
+
+ @ApiOperation(value = "This method is used to get the current date.")
+ @GetMapping("/getDate")
+ public LocalDate getDate() {
+ return LocalDate.now();
+ }
+
+ @ApiOperation(value = "This method is used to get the current time.")
+ @GetMapping("/getTime")
+ public LocalTime getTime() {
+ return LocalTime.now();
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/excluded/ErrorControllerExcludedForPackage.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/excluded/ErrorControllerExcludedForPackage.java
new file mode 100644
index 0000000000..a3b5a3fe63
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerconf/excluded/ErrorControllerExcludedForPackage.java
@@ -0,0 +1,17 @@
+package com.baeldung.swaggerconf.excluded;
+
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Component
+@RequestMapping("good-path/error-excluded-package")
+public class ErrorControllerExcludedForPackage extends BasicErrorController {
+
+ public ErrorControllerExcludedForPackage(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
+ super(errorAttributes, serverProperties.getError());
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerenums/SwaggerEnumsApplication.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerenums/SwaggerEnumsApplication.java
new file mode 100644
index 0000000000..0bb80df65f
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerenums/SwaggerEnumsApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.swaggerenums;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SwaggerEnumsApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SwaggerEnumsApplication.class, args);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerenums/controller/HireController.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerenums/controller/HireController.java
new file mode 100644
index 0000000000..7b7bfdc2c5
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerenums/controller/HireController.java
@@ -0,0 +1,22 @@
+package com.baeldung.swaggerenums.controller;
+
+import com.baeldung.swaggerenums.model.Employee;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+@Api
+@Path(value="/hire")
+@Produces({"application/json"})
+public class HireController {
+
+ @POST
+ @ApiOperation(value = "This method is used to hire employee with a specific role")
+ public String hireEmployee(@ApiParam(value = "role", required = true) Employee employee) {
+ return String.format("Hired for role: %s", employee.role.name());
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerenums/model/Employee.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerenums/model/Employee.java
new file mode 100644
index 0000000000..ce9e5089c9
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerenums/model/Employee.java
@@ -0,0 +1,17 @@
+package com.baeldung.swaggerenums.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+@ApiModel
+public class Employee {
+ @ApiModelProperty
+ public Role role;
+
+ public Role getRole() {
+ return role;
+ }
+ public void setRole(Role role) {
+ this.role = role;
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerenums/model/Role.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerenums/model/Role.java
new file mode 100644
index 0000000000..fa62b2d0b5
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerenums/model/Role.java
@@ -0,0 +1,8 @@
+package com.baeldung.swaggerenums.model;
+
+import io.swagger.annotations.ApiModel;
+
+@ApiModel
+public enum Role {
+ Engineer, Clerk, Driver, Janitor;
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerexample/SwaggerExampleApplication.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerexample/SwaggerExampleApplication.java
new file mode 100644
index 0000000000..4859224bb2
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerexample/SwaggerExampleApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.swaggerexample;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SwaggerExampleApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SwaggerExampleApplication.class, args);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerexample/config/SwaggerConfig.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerexample/config/SwaggerConfig.java
new file mode 100644
index 0000000000..32dbe87bae
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerexample/config/SwaggerConfig.java
@@ -0,0 +1,38 @@
+package com.baeldung.swaggerexample.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+
+import java.util.Collections;
+
+@Configuration
+@EnableWebMvc
+public class SwaggerConfig {
+
+ @Bean
+ public Docket api() {
+ return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
+ .select()
+ .apis(RequestHandlerSelectors.basePackage("com.baeldung.swaggerexample"))
+ .paths(PathSelectors.any())
+ .build();
+ }
+
+ private ApiInfo apiInfo() {
+ return new ApiInfo(
+ "Products API",
+ "API to let you add and view product",
+ "0.0.1",
+ "Terms of service",
+ new Contact("John Doe", "www.example.com", "myemail@company.com"),
+ "License of API", "API license URL", Collections.emptyList());
+ }
+}
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerexample/controller/ProductController.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerexample/controller/ProductController.java
new file mode 100644
index 0000000000..7500c01bc0
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerexample/controller/ProductController.java
@@ -0,0 +1,34 @@
+package com.baeldung.swaggerexample.controller;
+
+import com.baeldung.swaggerexample.entity.Product;
+import io.swagger.annotations.*;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@ApiOperation("Products API")
+public class ProductController {
+
+ @ApiOperation(value = "Create a new product", notes = "Creates a new product as per the request body")
+ @ApiResponses(value = {
+ @ApiResponse(code = 201, message = "Successfully created"),
+ @ApiResponse(code = 400, message = "Bad request - The product is not valid"),
+ @ApiResponse(code = 500, message = "Internal server error - Something went wrong")
+ })
+ @PostMapping(value = "/products")
+ public ResponseEntity createProduct(@RequestBody Product product) {
+ return new ResponseEntity<>(HttpStatus.CREATED);
+ }
+
+ @ApiOperation(value = "Get a product by id", notes = "Returns a product as per the id")
+ @ApiResponses(value = {
+ @ApiResponse(code = 200, message = "Successfully retrieved"),
+ @ApiResponse(code = 404, message = "Not found - The product was not found")
+ })
+ @GetMapping("/products/{id}")
+ public ResponseEntity getProduct(@PathVariable("id") @ApiParam(name = "id", value = "Product id", example = "1") Long id) {
+ //retrieval logic
+ return ResponseEntity.ok(new Product(1, "Product 1", "$21.99"));
+ }
+}
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerexample/entity/Product.java b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerexample/entity/Product.java
new file mode 100644
index 0000000000..122addadd5
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/main/java/com/baeldung/swaggerexample/entity/Product.java
@@ -0,0 +1,46 @@
+package com.baeldung.swaggerexample.entity;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+public class Product implements Serializable {
+ @ApiModelProperty(notes = "Product ID", example = "1", required = true)
+ private Long id;
+ @ApiModelProperty(notes = "Product name", example = "Product 1", required = false)
+ private String name;
+ @ApiModelProperty(notes = "Product price", example = "$100.00", required = true)
+ private String price;
+
+ // constructor and getter/setters
+
+ public Product(long id, String name, String price) {
+ this.id = id;
+ this.name = name;
+ this.price = price;
+ }
+
+ 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;
+ }
+
+ public String getPrice() {
+ return price;
+ }
+
+ public void setPrice(String price) {
+ this.price = price;
+ }
+}
diff --git a/spring-boot-modules/spring-boot-swagger/src/main/resources/application.properties b/spring-boot-modules/spring-boot-swagger/src/main/resources/application.properties
index e69de29bb2..a4e98652bc 100644
--- a/spring-boot-modules/spring-boot-swagger/src/main/resources/application.properties
+++ b/spring-boot-modules/spring-boot-swagger/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.mvc.pathmatch.matching-strategy = ANT_PATH_MATCHER
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-swagger/src/test/java/com/baeldung/swaggerconf/SwaggerConfExcludeErrorControllerIntegrationTest.java b/spring-boot-modules/spring-boot-swagger/src/test/java/com/baeldung/swaggerconf/SwaggerConfExcludeErrorControllerIntegrationTest.java
new file mode 100644
index 0000000000..c0a5c54d69
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/test/java/com/baeldung/swaggerconf/SwaggerConfExcludeErrorControllerIntegrationTest.java
@@ -0,0 +1,33 @@
+package com.baeldung.swaggerconf;
+
+import org.junit.Assert;
+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.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.ResultActions;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@AutoConfigureMockMvc
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class SwaggerConfExcludeErrorControllerIntegrationTest {
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Test
+ public void whenCallingSwaggerJSON_stringObjectDoesNotContainAnyErrorControllers() throws Exception {
+ ResultActions resultActions = mvc.perform(get("/v2/api-docs")).andExpect(status().isOk());
+ MvcResult result = resultActions.andReturn();
+ String content = result.getResponse().getContentAsString();
+ Assert.assertNotNull(content);
+ Assert.assertFalse(content.contains("error-controller"));
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-swagger/src/test/java/com/baeldung/swaggerenums/controller/HireControllerUnitTest.java b/spring-boot-modules/spring-boot-swagger/src/test/java/com/baeldung/swaggerenums/controller/HireControllerUnitTest.java
new file mode 100644
index 0000000000..b2ec5ccaa5
--- /dev/null
+++ b/spring-boot-modules/spring-boot-swagger/src/test/java/com/baeldung/swaggerenums/controller/HireControllerUnitTest.java
@@ -0,0 +1,25 @@
+package com.baeldung.swaggerenums.controller;
+
+import com.baeldung.swaggerenums.model.Employee;
+import com.baeldung.swaggerenums.model.Role;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HireControllerUnitTest {
+
+ @Test
+ public void givenRoleEngineer_whenHireEmployee_thenReturnsRoleInString() {
+ //Arrange
+ Role testRole = Role.Engineer;
+ Employee employee = new Employee();
+ employee.setRole(testRole);
+
+ //Act
+ HireController hireController = new HireController();
+ String response = hireController.hireEmployee(employee);
+
+ //Assert
+ Assert.assertEquals(String.format("Hired for role: %s", testRole),
+ response);
+ }
+}
diff --git a/spring-boot-modules/spring-boot-testing/pom.xml b/spring-boot-modules/spring-boot-testing/pom.xml
index 658eb7728e..fcfc2364ba 100644
--- a/spring-boot-modules/spring-boot-testing/pom.xml
+++ b/spring-boot-modules/spring-boot-testing/pom.xml
@@ -14,6 +14,18 @@
1.0.0-SNAPSHOT
+
+
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
+
+
+
org.springframework.boot
@@ -131,6 +143,7 @@
1.60.7.22.5.0
+ 2.17.1
\ No newline at end of file
diff --git a/spring-cloud/pom.xml b/spring-cloud/pom.xml
index ac810c9a77..75010fbd34 100644
--- a/spring-cloud/pom.xml
+++ b/spring-cloud/pom.xml
@@ -45,9 +45,11 @@
spring-cloud-ribbon-retryspring-cloud-circuit-breakerspring-cloud-eureka-self-preservation
-
+
+
spring-cloud-sentinelspring-cloud-dapr
+ spring-cloud-docker
diff --git a/spring-cloud/spring-cloud-archaius/zookeeper-config/pom.xml b/spring-cloud/spring-cloud-archaius/zookeeper-config/pom.xml
index 7dfb2c7436..7700a2219d 100644
--- a/spring-cloud/spring-cloud-archaius/zookeeper-config/pom.xml
+++ b/spring-cloud/spring-cloud-archaius/zookeeper-config/pom.xml
@@ -45,6 +45,7 @@
2.0.0.RELEASE3.4.13
+ 2.17.1
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-contract/pom.xml b/spring-cloud/spring-cloud-contract/pom.xml
index 8546e76586..6e8b130ed1 100644
--- a/spring-cloud/spring-cloud-contract/pom.xml
+++ b/spring-cloud/spring-cloud-contract/pom.xml
@@ -21,6 +21,13 @@
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+ org.springframework.bootspring-boot-starter-web
@@ -55,6 +62,7 @@
2.1.1.RELEASE2.1.4.RELEASE
+ 2.17.1
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-eureka-self-preservation/pom.xml b/spring-cloud/spring-cloud-eureka-self-preservation/pom.xml
index 0b7da2ce4a..5000adc164 100644
--- a/spring-cloud/spring-cloud-eureka-self-preservation/pom.xml
+++ b/spring-cloud/spring-cloud-eureka-self-preservation/pom.xml
@@ -20,6 +20,18 @@
spring-cloud-eureka-client
+
+
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
+
+
+
org.springframework.boot
@@ -32,6 +44,7 @@
2.1.3.RELEASEGreenwich.SR3
+ 2.17.1
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-eureka/pom.xml b/spring-cloud/spring-cloud-eureka/pom.xml
index e5327d4530..63190f7f20 100644
--- a/spring-cloud/spring-cloud-eureka/pom.xml
+++ b/spring-cloud/spring-cloud-eureka/pom.xml
@@ -22,6 +22,18 @@
spring-cloud-eureka-feign-client-integration-test
+
+
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
+
+
+
org.springframework.boot
@@ -34,6 +46,7 @@
2.1.2.RELEASEGreenwich.RELEASE
+ 2.17.1
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-functions/pom.xml b/spring-cloud/spring-cloud-functions/pom.xml
index 3dc68e2824..d9f90c7a56 100644
--- a/spring-cloud/spring-cloud-functions/pom.xml
+++ b/spring-cloud/spring-cloud-functions/pom.xml
@@ -10,7 +10,7 @@
jarDemo project for Spring Cloud Function
-
+ com.baeldung.spring.cloudspring-cloud1.0.0-SNAPSHOT
diff --git a/spring-cloud/spring-cloud-gateway/README-OAuth.md b/spring-cloud/spring-cloud-gateway/README-OAuth.md
new file mode 100644
index 0000000000..c186114589
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/README-OAuth.md
@@ -0,0 +1,40 @@
+# OAuth Test Setup
+
+In order to test the OAuth-secured gateway configurations, please follow the steps below
+
+## Keycloak setup
+
+1. Clone or download the https://github.com/Baeldung/spring-security-oauth project
+2. Replace the file `oauth-rest/oauth-authorization-server/src/main/resources/baeldung-realm.json`
+ with the one provider here
+3. Go to the oauth-rest/oauth-authorization-server folder and use maven to build the project
+4. Run the Keycloack service with `mvn spring-boot:run`
+5. Once Keycloak is up and running, go to `http://localhost:8083/auth/admin/master/console/#/realms/baeldung` and
+ log in with using `bael-admin/pass` as credentials
+6. Create two test users, so that one belongs to the *Golden Customers* group and the other doesn't.
+
+## Quotes backend
+
+Use the provided maven profile:
+
+```
+$ mvn spring-boot:run -Pquotes-application
+```
+
+## Gateway as Resource Server
+
+Use the provided maven profile:
+
+```
+$ mvn spring-boot:run -Pgateway-as-resource-server
+```
+
+## Gateway as OAuth 2.0 Client
+
+Use the provided maven profile:
+
+```
+$ mvn spring-boot:run -Pgateway-as-oauth-client
+```
+
+
diff --git a/spring-cloud/spring-cloud-gateway/README.md b/spring-cloud/spring-cloud-gateway/README.md
index 90e81fe9a2..808536ce80 100644
--- a/spring-cloud/spring-cloud-gateway/README.md
+++ b/spring-cloud/spring-cloud-gateway/README.md
@@ -3,7 +3,10 @@
This module contains articles about Spring Cloud Gateway
### Relevant Articles:
+
- [Exploring the new Spring Cloud Gateway](http://www.baeldung.com/spring-cloud-gateway)
- [Writing Custom Spring Cloud Gateway Filters](https://www.baeldung.com/spring-cloud-custom-gateway-filters)
- [Spring Cloud Gateway Routing Predicate Factories](https://www.baeldung.com/spring-cloud-gateway-routing-predicate-factories)
- [Spring Cloud Gateway WebFilter Factories](https://www.baeldung.com/spring-cloud-gateway-webfilter-factories)
+- [Using Spring Cloud Gateway with OAuth 2.0 Patterns](https://www.baeldung.com/spring-cloud-gateway-oauth2)
+- [URL Rewriting With Spring Cloud Gateway](https://www.baeldung.com/spring-cloud-gateway-url-rewriting)
diff --git a/spring-cloud/spring-cloud-gateway/baeldung-realm.json b/spring-cloud/spring-cloud-gateway/baeldung-realm.json
new file mode 100644
index 0000000000..4dad262568
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/baeldung-realm.json
@@ -0,0 +1,2186 @@
+{
+ "id": "baeldung",
+ "realm": "baeldung",
+ "notBefore": 0,
+ "revokeRefreshToken": false,
+ "refreshTokenMaxReuse": 0,
+ "accessTokenLifespan": 300,
+ "accessTokenLifespanForImplicitFlow": 900,
+ "ssoSessionIdleTimeout": 1800,
+ "ssoSessionMaxLifespan": 36000,
+ "ssoSessionIdleTimeoutRememberMe": 0,
+ "ssoSessionMaxLifespanRememberMe": 0,
+ "offlineSessionIdleTimeout": 2592000,
+ "offlineSessionMaxLifespanEnabled": false,
+ "offlineSessionMaxLifespan": 5184000,
+ "clientSessionIdleTimeout": 0,
+ "clientSessionMaxLifespan": 0,
+ "clientOfflineSessionIdleTimeout": 0,
+ "clientOfflineSessionMaxLifespan": 0,
+ "accessCodeLifespan": 60,
+ "accessCodeLifespanUserAction": 300,
+ "accessCodeLifespanLogin": 1800,
+ "actionTokenGeneratedByAdminLifespan": 43200,
+ "actionTokenGeneratedByUserLifespan": 300,
+ "enabled": true,
+ "sslRequired": "external",
+ "registrationAllowed": false,
+ "registrationEmailAsUsername": false,
+ "rememberMe": false,
+ "verifyEmail": false,
+ "loginWithEmailAllowed": true,
+ "duplicateEmailsAllowed": false,
+ "resetPasswordAllowed": false,
+ "editUsernameAllowed": false,
+ "bruteForceProtected": false,
+ "permanentLockout": false,
+ "maxFailureWaitSeconds": 900,
+ "minimumQuickLoginWaitSeconds": 60,
+ "waitIncrementSeconds": 60,
+ "quickLoginCheckMilliSeconds": 1000,
+ "maxDeltaTimeSeconds": 43200,
+ "failureFactor": 30,
+ "roles": {
+ "realm": [
+ {
+ "id": "2f721e46-398b-43ff-845f-3c747f1f69f5",
+ "name": "standard",
+ "description": "Standard customer",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "baeldung",
+ "attributes": {}
+ },
+ {
+ "id": "b8b1cb49-c953-4cb0-acea-62b24c4b6d30",
+ "name": "silver",
+ "description": "Silver customer",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "baeldung",
+ "attributes": {}
+ },
+ {
+ "id": "f44f5ba9-2393-4d2a-b581-81cf8dd7f245",
+ "name": "gold",
+ "description": "Gold customers",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "baeldung",
+ "attributes": {}
+ },
+ {
+ "id": "3b6109f5-6e5a-4578-83c3-791ec3e2bf9e",
+ "name": "offline_access",
+ "description": "${role_offline-access}",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "baeldung",
+ "attributes": {}
+ },
+ {
+ "id": "0dd6a8c7-d669-4941-9ea1-521980e9c53f",
+ "name": "uma_authorization",
+ "description": "${role_uma_authorization}",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "baeldung",
+ "attributes": {}
+ },
+ {
+ "id": "ca962095-7f9b-49e2-a190-e391a0d4b704",
+ "name": "user",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "baeldung",
+ "attributes": {}
+ }
+ ],
+ "client": {
+ "newClient": [],
+ "realm-management": [
+ {
+ "id": "5d00243f-ceec-4b0c-995e-d86d5b8a0ae6",
+ "name": "view-clients",
+ "description": "${role_view-clients}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-clients"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "941612de-bd85-47a5-8dfa-37c270dde28c",
+ "name": "view-authorization",
+ "description": "${role_view-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "5ea9810d-63cc-4277-9b32-ba8a3d3c6091",
+ "name": "manage-realm",
+ "description": "${role_manage-realm}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "8b7b0dd8-350b-473e-b8cd-8acad34f1358",
+ "name": "query-clients",
+ "description": "${role_query-clients}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "0f8e5ee8-b014-4b7c-9b69-50f46abcba5f",
+ "name": "query-groups",
+ "description": "${role_query-groups}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "911b1489-9383-4734-b134-bf49bf992ce9",
+ "name": "manage-clients",
+ "description": "${role_manage-clients}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "5d48274c-bd6b-4c26-ad54-f1a2254beac0",
+ "name": "view-realm",
+ "description": "${role_view-realm}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "3ea43b64-316f-4693-8346-9ee78b24adaf",
+ "name": "manage-identity-providers",
+ "description": "${role_manage-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "49735614-96ec-49b2-98fe-3af9bcd1a33a",
+ "name": "create-client",
+ "description": "${role_create-client}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "e8f8c3cc-0ff1-4f72-a271-db6821a3cdb6",
+ "name": "manage-users",
+ "description": "${role_manage-users}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "387418b1-4f80-4b00-b9dd-805ca041f805",
+ "name": "view-identity-providers",
+ "description": "${role_view-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "427c27d4-521a-464b-a0df-16d7f537e8d5",
+ "name": "realm-admin",
+ "description": "${role_realm-admin}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "view-clients",
+ "view-authorization",
+ "query-groups",
+ "manage-realm",
+ "query-clients",
+ "manage-clients",
+ "view-realm",
+ "manage-identity-providers",
+ "create-client",
+ "manage-users",
+ "view-identity-providers",
+ "query-users",
+ "query-realms",
+ "view-users",
+ "impersonation",
+ "manage-authorization",
+ "manage-events",
+ "view-events"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "a574cf01-03e4-4573-ab9e-276d13a1ce8d",
+ "name": "query-users",
+ "description": "${role_query-users}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "c3a253a8-a1b6-4d38-9677-f728f32482ad",
+ "name": "query-realms",
+ "description": "${role_query-realms}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "f3cb93da-273e-419a-b2f4-93f09896abcf",
+ "name": "view-users",
+ "description": "${role_view-users}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-users",
+ "query-groups"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "6eedf2b7-50ef-4495-a89b-54aef751b7fa",
+ "name": "manage-authorization",
+ "description": "${role_manage-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "0332e99b-3dfc-4193-9e13-5728f8f3e6d6",
+ "name": "impersonation",
+ "description": "${role_impersonation}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "b690cb9c-0f4a-4be5-ade0-b40443d8149d",
+ "name": "view-events",
+ "description": "${role_view-events}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ },
+ {
+ "id": "aac3def5-f193-4a6c-9065-1667a0746a8a",
+ "name": "manage-events",
+ "description": "${role_manage-events}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "attributes": {}
+ }
+ ],
+ "security-admin-console": [],
+ "quotes-client": [],
+ "admin-cli": [],
+ "account-console": [],
+ "broker": [
+ {
+ "id": "397b5703-4c81-48fd-a24c-a7e8177ef657",
+ "name": "read-token",
+ "description": "${role_read-token}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "4b9609f0-48d1-4e71-9381-2ecec08616f9",
+ "attributes": {}
+ }
+ ],
+ "account": [
+ {
+ "id": "8daa8096-d14e-4d1c-ad1f-83f822016aa1",
+ "name": "manage-account",
+ "description": "${role_manage-account}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "account": [
+ "manage-account-links"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145",
+ "attributes": {}
+ },
+ {
+ "id": "15f0d8be-932b-4565-8ad0-e8aa170093dd",
+ "name": "view-consent",
+ "description": "${role_view-consent}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145",
+ "attributes": {}
+ },
+ {
+ "id": "c5aa697c-bf87-47c6-bd94-9121b72420b9",
+ "name": "view-applications",
+ "description": "${role_view-applications}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145",
+ "attributes": {}
+ },
+ {
+ "id": "f0b3bbe4-aec1-4227-b9d3-2c314d612a04",
+ "name": "manage-consent",
+ "description": "${role_manage-consent}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "account": [
+ "view-consent"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145",
+ "attributes": {}
+ },
+ {
+ "id": "948269c7-a69c-4c82-a7f3-88868713dfd9",
+ "name": "manage-account-links",
+ "description": "${role_manage-account-links}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145",
+ "attributes": {}
+ },
+ {
+ "id": "aed18201-2433-4998-8fa3-0979b0b31c10",
+ "name": "view-profile",
+ "description": "${role_view-profile}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145",
+ "attributes": {}
+ }
+ ]
+ }
+ },
+ "groups": [
+ {
+ "id": "635c3314-f15f-4c02-bcb7-8739fd4c21b7",
+ "name": "golden_customers",
+ "path": "/golden_customers",
+ "attributes": {},
+ "realmRoles": [
+ "gold"
+ ],
+ "clientRoles": {},
+ "subGroups": []
+ },
+ {
+ "id": "279c5ec4-0588-4884-91c1-2697ed5c9826",
+ "name": "silver_customers",
+ "path": "/silver_customers",
+ "attributes": {},
+ "realmRoles": [
+ "silver"
+ ],
+ "clientRoles": {},
+ "subGroups": []
+ }
+ ],
+ "defaultRoles": [
+ "uma_authorization",
+ "offline_access"
+ ],
+ "requiredCredentials": [
+ "password"
+ ],
+ "otpPolicyType": "totp",
+ "otpPolicyAlgorithm": "HmacSHA1",
+ "otpPolicyInitialCounter": 0,
+ "otpPolicyDigits": 6,
+ "otpPolicyLookAheadWindow": 1,
+ "otpPolicyPeriod": 30,
+ "otpSupportedApplications": [
+ "FreeOTP",
+ "Google Authenticator"
+ ],
+ "webAuthnPolicyRpEntityName": "keycloak",
+ "webAuthnPolicySignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyRpId": "",
+ "webAuthnPolicyAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyRequireResidentKey": "not specified",
+ "webAuthnPolicyUserVerificationRequirement": "not specified",
+ "webAuthnPolicyCreateTimeout": 0,
+ "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyAcceptableAaguids": [],
+ "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
+ "webAuthnPolicyPasswordlessSignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyPasswordlessRpId": "",
+ "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
+ "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
+ "webAuthnPolicyPasswordlessCreateTimeout": 0,
+ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyPasswordlessAcceptableAaguids": [],
+ "scopeMappings": [
+ {
+ "clientScope": "offline_access",
+ "roles": [
+ "offline_access"
+ ]
+ }
+ ],
+ "clientScopeMappings": {
+ "account": [
+ {
+ "client": "account-console",
+ "roles": [
+ "manage-account"
+ ]
+ }
+ ]
+ },
+ "clients": [
+ {
+ "id": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145",
+ "clientId": "account",
+ "name": "${client_account}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/baeldung/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "defaultRoles": [
+ "manage-account",
+ "view-profile"
+ ],
+ "redirectUris": [
+ "/realms/baeldung/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "role_list",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "8209784b-8540-43c2-aece-241acf12ea5a",
+ "clientId": "account-console",
+ "name": "${client_account-console}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/baeldung/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [
+ "/realms/baeldung/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "id": "58395b96-1718-4787-8f92-74577e2bfc30",
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "role_list",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "13d76feb-d762-4409-bb84-7a75bc395a61",
+ "clientId": "admin-cli",
+ "name": "${client_admin-cli}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": false,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": true,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "role_list",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "4b9609f0-48d1-4e71-9381-2ecec08616f9",
+ "clientId": "broker",
+ "name": "${client_broker}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "role_list",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "b88ce206-63d6-43b6-87c9-ea09d8c02f32",
+ "clientId": "newClient",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [
+ "http://localhost:8082/new-client/login/oauth2/code/custom",
+ "http://localhost:8089/",
+ "http://localhost:8089/auth/redirect/"
+ ],
+ "webOrigins": [
+ "+"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": true,
+ "directAccessGrantsEnabled": true,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "saml.assertion.signature": "false",
+ "saml.force.post.binding": "false",
+ "saml.multivalued.roles": "false",
+ "saml.encrypt": "false",
+ "saml.server.signature": "false",
+ "saml.server.signature.keyinfo.ext": "false",
+ "exclude.session.state.from.auth.response": "false",
+ "saml_force_name_id_format": "false",
+ "saml.client.signature": "false",
+ "tls.client.certificate.bound.access.tokens": "false",
+ "saml.authnstatement": "false",
+ "display.on.consent.screen": "false",
+ "saml.onetimeuse.condition": "false"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": true,
+ "nodeReRegistrationTimeout": -1,
+ "defaultClientScopes": [
+ "role_list",
+ "profile"
+ ],
+ "optionalClientScopes": [
+ "web-origins",
+ "address",
+ "read",
+ "phone",
+ "roles",
+ "offline_access",
+ "microprofile-jwt",
+ "write",
+ "email"
+ ]
+ },
+ {
+ "id": "5898f71f-b91e-4c3f-9c86-48de0e8665c4",
+ "clientId": "quotes-client",
+ "name": "Quotes Client",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "0e082231-a70d-48e8-b8a5-fbfb743041b6",
+ "redirectUris": [
+ "http://localhost:8082/*",
+ "http://localhost:8087/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": true,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "saml.assertion.signature": "false",
+ "saml.multivalued.roles": "false",
+ "saml.force.post.binding": "false",
+ "saml.encrypt": "false",
+ "saml.server.signature": "false",
+ "saml.server.signature.keyinfo.ext": "false",
+ "exclude.session.state.from.auth.response": "false",
+ "saml_force_name_id_format": "false",
+ "saml.client.signature": "false",
+ "tls.client.certificate.bound.access.tokens": "false",
+ "saml.authnstatement": "false",
+ "display.on.consent.screen": "false",
+ "saml.onetimeuse.condition": "false"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": true,
+ "nodeReRegistrationTimeout": -1,
+ "protocolMappers": [
+ {
+ "id": "008d32b5-ea7b-4739-89af-e90fe137bda9",
+ "name": "realm roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "realm_access.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "role_list",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "6a4bfbd0-576d-4778-af56-56f876647355",
+ "clientId": "realm-management",
+ "name": "${client_realm-management}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": true,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "role_list",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "8e358d2f-b085-4243-8e6e-c175431e5eeb",
+ "clientId": "security-admin-console",
+ "name": "${client_security-admin-console}",
+ "rootUrl": "${authAdminUrl}",
+ "baseUrl": "/admin/baeldung/console/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [
+ "/admin/baeldung/console/*"
+ ],
+ "webOrigins": [
+ "+"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "id": "9cfca9ee-493d-4b5e-8170-2d364149de59",
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "role_list",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ }
+ ],
+ "clientScopes": [
+ {
+ "id": "77c7e29d-1a22-4419-bbfb-4a62bb033449",
+ "name": "address",
+ "description": "OpenID Connect built-in scope: address",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${addressScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "94e1879d-b49e-4178-96e0-bf8d7f32c160",
+ "name": "address",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-address-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute.formatted": "formatted",
+ "user.attribute.country": "country",
+ "user.attribute.postal_code": "postal_code",
+ "userinfo.token.claim": "true",
+ "user.attribute.street": "street",
+ "id.token.claim": "true",
+ "user.attribute.region": "region",
+ "access.token.claim": "true",
+ "user.attribute.locality": "locality"
+ }
+ }
+ ]
+ },
+ {
+ "id": "b3526ac1-10e2-4344-8621-9c5a0853e97a",
+ "name": "email",
+ "description": "OpenID Connect built-in scope: email",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${emailScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "d30270dc-baa6-455a-8ff6-ddccf8a78d86",
+ "name": "email verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "emailVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email_verified",
+ "jsonType.label": "boolean"
+ }
+ },
+ {
+ "id": "f5b1684d-e479-4134-8578-457fa64717da",
+ "name": "email",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "email",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "id": "c658ae14-e96a-4745-b21b-2ed5c4c63f5f",
+ "name": "microprofile-jwt",
+ "description": "Microprofile - JWT built-in scope",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "false"
+ },
+ "protocolMappers": [
+ {
+ "id": "959521bc-5ffd-465b-95f2-5b0c20d1909c",
+ "name": "upn",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "upn",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "07b8550c-b298-4cce-9ffb-900182575b76",
+ "name": "groups",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "multivalued": "true",
+ "userinfo.token.claim": "true",
+ "user.attribute": "foo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "groups",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "id": "569b3d44-4ecd-4768-a58c-70ff38f4b4fe",
+ "name": "offline_access",
+ "description": "OpenID Connect built-in scope: offline_access",
+ "protocol": "openid-connect",
+ "attributes": {
+ "consent.screen.text": "${offlineAccessScopeConsentText}",
+ "display.on.consent.screen": "true"
+ }
+ },
+ {
+ "id": "a3e7b19d-df6c-437e-9eea-06fec1becb2f",
+ "name": "phone",
+ "description": "OpenID Connect built-in scope: phone",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${phoneScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "72a070f7-4363-4c88-8153-6fd2d12b9b04",
+ "name": "phone number verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumberVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number_verified",
+ "jsonType.label": "boolean"
+ }
+ },
+ {
+ "id": "24b42c6d-a93c-4aa1-9a03-2a2b55954c13",
+ "name": "phone number",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumber",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "id": "ba8c9950-fd0b-4434-8be6-b58456d7b6d4",
+ "name": "profile",
+ "description": "OpenID Connect built-in scope: profile",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${profileScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "0a9ddd71-309c-40f0-8ea6-a0791070c6ed",
+ "name": "profile",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "profile",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "profile",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "fbf53bbd-1ad0-4bf8-8030-50f81696d8ee",
+ "name": "nickname",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "nickname",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "nickname",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "423be2cd-42c0-462e-9030-18f9b28ff2d3",
+ "name": "gender",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "gender",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "gender",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "53eb9006-4b81-474a-8b60-80f775d54b63",
+ "name": "picture",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "picture",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "picture",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "4d8bc82a-eaeb-499e-8eb2-0f1dcbe91699",
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "d3b25485-4042-419d-afff-cfd63a76e229",
+ "name": "full name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-full-name-mapper",
+ "consentRequired": false,
+ "config": {
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "userinfo.token.claim": "true"
+ }
+ },
+ {
+ "id": "422cfa5a-f2f4-4f36-82df-91b47ae1ea50",
+ "name": "given name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "firstName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "given_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "3f2863c1-d98d-45b5-b08f-af9c4d9c10f8",
+ "name": "website",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "website",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "website",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "c98c063d-eee4-41a0-9130-595afd709d1f",
+ "name": "middle name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "middleName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "middle_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "8dbed80a-d672-4185-8dda-4bba2a56ec83",
+ "name": "family name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "lastName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "family_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "5e5c690c-93cf-489d-a054-b109eab8911b",
+ "name": "updated at",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "updatedAt",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "updated_at",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "3b985202-af8a-42f1-ac5f-0966a404f5d7",
+ "name": "birthdate",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "birthdate",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "birthdate",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "6eafd1b3-7121-4919-ad1e-039fa58acc32",
+ "name": "username",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "preferred_username",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "73cba925-8c31-443f-9601-b1514e6396c1",
+ "name": "zoneinfo",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "zoneinfo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "zoneinfo",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "id": "c1a2eb23-25c6-4be7-a791-bbdca99c83f7",
+ "name": "read",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true"
+ }
+ },
+ {
+ "id": "18e141bf-dabe-4858-879c-dbc439cdead4",
+ "name": "role_list",
+ "description": "SAML role list",
+ "protocol": "saml",
+ "attributes": {
+ "consent.screen.text": "${samlRoleListScopeConsentText}",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "id": "10cbe37f-0198-4d65-bc8a-bfe5ad8145d1",
+ "name": "role list",
+ "protocol": "saml",
+ "protocolMapper": "saml-role-list-mapper",
+ "consentRequired": false,
+ "config": {
+ "single": "false",
+ "attribute.nameformat": "Basic",
+ "attribute.name": "Role"
+ }
+ }
+ ]
+ },
+ {
+ "id": "111ed87a-5fd3-4cee-96df-8dbfb88cfdc0",
+ "name": "roles",
+ "description": "OpenID Connect scope for add user roles to the access token",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "false",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${rolesScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "24924d8d-6071-4a93-b40f-326176cb335e",
+ "name": "realm roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "realm_access.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ },
+ {
+ "id": "2f6a9bdf-3758-484c-996d-e4f93555559f",
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ },
+ {
+ "id": "804d4798-d9a3-4fd3-8b28-d12142e8cb3d",
+ "name": "client roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-client-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "resource_access.${client_id}.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ }
+ ]
+ },
+ {
+ "id": "51d49314-b511-43e0-9258-bfb873758a78",
+ "name": "web-origins",
+ "description": "OpenID Connect scope for add allowed web origins to the access token",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "false",
+ "display.on.consent.screen": "false",
+ "consent.screen.text": ""
+ },
+ "protocolMappers": [
+ {
+ "id": "2b384cd0-9e85-4a87-8eeb-2b480b0587b7",
+ "name": "allowed web origins",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-allowed-origins-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ]
+ },
+ {
+ "id": "c3e253fb-7361-47cf-9d4a-86245686fdf1",
+ "name": "write",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true"
+ }
+ }
+ ],
+ "defaultDefaultClientScopes": [
+ "roles",
+ "role_list",
+ "web-origins",
+ "email",
+ "profile"
+ ],
+ "defaultOptionalClientScopes": [
+ "offline_access",
+ "address",
+ "phone",
+ "microprofile-jwt"
+ ],
+ "browserSecurityHeaders": {
+ "contentSecurityPolicyReportOnly": "",
+ "xContentTypeOptions": "nosniff",
+ "xRobotsTag": "none",
+ "xFrameOptions": "SAMEORIGIN",
+ "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+ "xXSSProtection": "1; mode=block",
+ "strictTransportSecurity": "max-age=31536000; includeSubDomains"
+ },
+ "smtpServer": {},
+ "eventsEnabled": false,
+ "eventsListeners": [
+ "jboss-logging"
+ ],
+ "enabledEventTypes": [],
+ "adminEventsEnabled": false,
+ "adminEventsDetailsEnabled": false,
+ "components": {
+ "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
+ {
+ "id": "84305f42-4b6d-4b0a-ac7c-53e406e3ac63",
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "c7c38a95-744f-4558-a403-9cf692fe1944",
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "365b2899-befe-4417-b89b-562650ec4446",
+ "name": "Full Scope Disabled",
+ "providerId": "scope",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "id": "81c32244-7921-43e9-9356-a3469259b78c",
+ "name": "Trusted Hosts",
+ "providerId": "trusted-hosts",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "host-sending-registration-request-must-match": [
+ "true"
+ ],
+ "client-uris-must-match": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "d09b2147-afea-4f7f-a49c-0aec7eee10de",
+ "name": "Max Clients Limit",
+ "providerId": "max-clients",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "max-clients": [
+ "200"
+ ]
+ }
+ },
+ {
+ "id": "41ffde1b-72a2-416f-87a7-94989e940dc0",
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "saml-user-property-mapper",
+ "oidc-address-mapper",
+ "oidc-usermodel-property-mapper",
+ "oidc-full-name-mapper",
+ "oidc-sha256-pairwise-sub-mapper",
+ "saml-role-list-mapper",
+ "oidc-usermodel-attribute-mapper",
+ "saml-user-attribute-mapper"
+ ]
+ }
+ },
+ {
+ "id": "76075388-2782-4656-a986-313493239a9f",
+ "name": "Consent Required",
+ "providerId": "consent-required",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "id": "3caaf57a-9cd7-48c1-b709-b40b887414f7",
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "oidc-usermodel-attribute-mapper",
+ "saml-user-property-mapper",
+ "oidc-usermodel-property-mapper",
+ "oidc-full-name-mapper",
+ "saml-user-attribute-mapper",
+ "oidc-address-mapper",
+ "saml-role-list-mapper",
+ "oidc-sha256-pairwise-sub-mapper"
+ ]
+ }
+ }
+ ],
+ "org.keycloak.keys.KeyProvider": [
+ {
+ "id": "d67a940a-52e4-44a5-9f69-6ffdd67a188f",
+ "name": "rsa-generated",
+ "providerId": "rsa-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ },
+ {
+ "id": "48d40de3-6234-42e8-9449-f68f56abb54b",
+ "name": "aes-generated",
+ "providerId": "aes-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ },
+ {
+ "id": "52ea1c5d-2a30-459f-b66a-249f298b32f8",
+ "name": "hmac-generated",
+ "providerId": "hmac-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ],
+ "algorithm": [
+ "HS256"
+ ]
+ }
+ }
+ ]
+ },
+ "internationalizationEnabled": false,
+ "supportedLocales": [],
+ "authenticationFlows": [
+ {
+ "id": "b35b726e-c1cc-4a31-8670-8c858c088498",
+ "alias": "Handle Existing Account",
+ "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-confirm-link",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "flowAlias": "Handle Existing Account - Alternatives - 0",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "b7cb4b8c-0064-4c6b-95ee-d7f50011bae7",
+ "alias": "Handle Existing Account - Alternatives - 0",
+ "description": "Subflow of Handle Existing Account with alternative executions",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-email-verification",
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "flowAlias": "Verify Existing Account by Re-authentication",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "2576bf21-a516-4e22-8ed8-8a1a3d35c06a",
+ "alias": "Verify Existing Account by Re-authentication",
+ "description": "Reauthentication of existing account",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-username-password-form",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "requirement": "CONDITIONAL",
+ "priority": 20,
+ "flowAlias": "Verify Existing Account by Re-authentication - auth-otp-form - Conditional",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "867b0355-c2b0-4f4e-b535-09406e2bc086",
+ "alias": "Verify Existing Account by Re-authentication - auth-otp-form - Conditional",
+ "description": "Flow to determine if the auth-otp-form authenticator should be used or not.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-otp-form",
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "52e45a6a-292f-4a34-9c02-7f97d9997a9c",
+ "alias": "browser",
+ "description": "browser based authentication",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "auth-cookie",
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-spnego",
+ "requirement": "DISABLED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "identity-provider-redirector",
+ "requirement": "ALTERNATIVE",
+ "priority": 25,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "requirement": "ALTERNATIVE",
+ "priority": 30,
+ "flowAlias": "forms",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "ef818d24-fb06-418a-a093-16239068cb58",
+ "alias": "clients",
+ "description": "Base authentication for clients",
+ "providerId": "client-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "client-secret",
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "client-jwt",
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "client-secret-jwt",
+ "requirement": "ALTERNATIVE",
+ "priority": 30,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "client-x509",
+ "requirement": "ALTERNATIVE",
+ "priority": 40,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "f7864004-be4b-40f2-8534-454981f09f98",
+ "alias": "direct grant",
+ "description": "OpenID Connect Resource Owner Grant",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "direct-grant-validate-username",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "direct-grant-validate-password",
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "requirement": "CONDITIONAL",
+ "priority": 30,
+ "flowAlias": "direct grant - direct-grant-validate-otp - Conditional",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "233225f2-78b0-4756-9568-a775ea7cc975",
+ "alias": "direct grant - direct-grant-validate-otp - Conditional",
+ "description": "Flow to determine if the direct-grant-validate-otp authenticator should be used or not.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "direct-grant-validate-otp",
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "2787939c-3312-4d56-9d61-22a71947e154",
+ "alias": "docker auth",
+ "description": "Used by Docker clients to authenticate against the IDP",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "docker-http-basic-authenticator",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "c80ca473-6e65-4140-a9d1-035414546bfb",
+ "alias": "first broker login",
+ "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticatorConfig": "review profile config",
+ "authenticator": "idp-review-profile",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "flowAlias": "first broker login - Alternatives - 0",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "700a71e1-f176-432e-9e70-ccb06d539730",
+ "alias": "first broker login - Alternatives - 0",
+ "description": "Subflow of first broker login with alternative executions",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticatorConfig": "create unique user config",
+ "authenticator": "idp-create-user-if-unique",
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "flowAlias": "Handle Existing Account",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "13cd2ee9-8ebb-4651-8a9d-535d4020d889",
+ "alias": "forms",
+ "description": "Username, password, otp and other auth forms.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "auth-username-password-form",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "requirement": "CONDITIONAL",
+ "priority": 20,
+ "flowAlias": "forms - auth-otp-form - Conditional",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "80067ebf-72b9-4ba5-abc3-161dfdd53938",
+ "alias": "forms - auth-otp-form - Conditional",
+ "description": "Flow to determine if the auth-otp-form authenticator should be used or not.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-otp-form",
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "269354b1-7dd7-46ff-8a69-084cd2c7be80",
+ "alias": "http challenge",
+ "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "no-cookie-redirect",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "basic-auth",
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "basic-auth-otp",
+ "requirement": "DISABLED",
+ "priority": 30,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-spnego",
+ "requirement": "DISABLED",
+ "priority": 40,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "c5fa64a0-e784-490f-8879-0e8a209e3636",
+ "alias": "registration",
+ "description": "registration flow",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "registration-page-form",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "flowAlias": "registration form",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "bc48f897-4d16-49a8-be70-183c7867e20a",
+ "alias": "registration form",
+ "description": "registration form",
+ "providerId": "form-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "registration-user-creation",
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "registration-profile-action",
+ "requirement": "REQUIRED",
+ "priority": 40,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "registration-password-action",
+ "requirement": "REQUIRED",
+ "priority": 50,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "registration-recaptcha-action",
+ "requirement": "DISABLED",
+ "priority": 60,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "fddaba41-87b1-40d1-b147-f062c38e39ad",
+ "alias": "reset credentials",
+ "description": "Reset credentials for a user if they forgot their password or something",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "reset-credentials-choose-user",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "reset-credential-email",
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "reset-password",
+ "requirement": "REQUIRED",
+ "priority": 30,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "requirement": "CONDITIONAL",
+ "priority": 40,
+ "flowAlias": "reset credentials - reset-otp - Conditional",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "6b9fa295-1eb4-48b0-a286-b17ebd6be7e1",
+ "alias": "reset credentials - reset-otp - Conditional",
+ "description": "Flow to determine if the reset-otp authenticator should be used or not.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "reset-otp",
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "c890d4c4-e4e2-4618-b69d-8d30f8278965",
+ "alias": "saml ecp",
+ "description": "SAML ECP Profile Authentication Flow",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "http-basic-authenticator",
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ }
+ ],
+ "authenticatorConfig": [
+ {
+ "id": "2b02fbe4-e05e-4238-bb0e-04da7d4efd4e",
+ "alias": "create unique user config",
+ "config": {
+ "require.password.update.after.registration": "false"
+ }
+ },
+ {
+ "id": "07dd933b-ce95-4a7c-bd81-3efa24f9558c",
+ "alias": "review profile config",
+ "config": {
+ "update.profile.on.first.login": "missing"
+ }
+ }
+ ],
+ "requiredActions": [
+ {
+ "alias": "CONFIGURE_TOTP",
+ "name": "Configure OTP",
+ "providerId": "CONFIGURE_TOTP",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 10,
+ "config": {}
+ },
+ {
+ "alias": "terms_and_conditions",
+ "name": "Terms and Conditions",
+ "providerId": "terms_and_conditions",
+ "enabled": false,
+ "defaultAction": false,
+ "priority": 20,
+ "config": {}
+ },
+ {
+ "alias": "UPDATE_PASSWORD",
+ "name": "Update Password",
+ "providerId": "UPDATE_PASSWORD",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 30,
+ "config": {}
+ },
+ {
+ "alias": "UPDATE_PROFILE",
+ "name": "Update Profile",
+ "providerId": "UPDATE_PROFILE",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 40,
+ "config": {}
+ },
+ {
+ "alias": "VERIFY_EMAIL",
+ "name": "Verify Email",
+ "providerId": "VERIFY_EMAIL",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 50,
+ "config": {}
+ },
+ {
+ "alias": "update_user_locale",
+ "name": "Update User Locale",
+ "providerId": "update_user_locale",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 1000,
+ "config": {}
+ }
+ ],
+ "browserFlow": "browser",
+ "registrationFlow": "registration",
+ "directGrantFlow": "direct grant",
+ "resetCredentialsFlow": "reset credentials",
+ "clientAuthenticationFlow": "clients",
+ "dockerAuthenticationFlow": "docker auth",
+ "attributes": {
+ "clientOfflineSessionMaxLifespan": "0",
+ "clientSessionIdleTimeout": "0",
+ "clientSessionMaxLifespan": "0",
+ "clientOfflineSessionIdleTimeout": "0"
+ },
+ "keycloakVersion": "11.0.2",
+ "userManagedAccessAllowed": false
+}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-gateway/pom.xml b/spring-cloud/spring-cloud-gateway/pom.xml
index 609ba54481..e8949cc039 100644
--- a/spring-cloud/spring-cloud-gateway/pom.xml
+++ b/spring-cloud/spring-cloud-gateway/pom.xml
@@ -83,23 +83,112 @@
org.springframework.bootspring-boot-devtools
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ ${java.version}
+ ${java.version}
+
+ org.springframework.bootspring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+ quotes-application
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ com.baeldung.springcloudgateway.oauth.backend.QuotesApplication
+
+
+
+
+
+
+ gateway-as-resource-server
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ com.baeldung.springcloudgateway.oauth.server.ResourceServerGatewayApplication
+ -Dspring.profiles.active=resource-server
+
+
+
+
+
+
+ gateway-as-oauth-client
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ com.baeldung.springcloudgateway.oauth.server.ResourceServerGatewayApplication
+ -Dspring.profiles.active=oauth-client
+
+
+
+
+
+
+ gateway-url-rewrite
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ com.baeldung.springcloudgateway.rewrite.URLRewriteGatewayApplication
+ -Dspring.profiles.active=url-rewrite
+
+
+
+
+
+
+
- Hoxton.SR3
- 2.2.6.RELEASE
+
+
6.0.2.Final0.7.2
- 5.5.2
+ 9.19
+
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/custompredicates/config/CustomPredicatesConfig.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/custompredicates/config/CustomPredicatesConfig.java
index 0e88b29bcf..ea58eb7e46 100644
--- a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/custompredicates/config/CustomPredicatesConfig.java
+++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/custompredicates/config/CustomPredicatesConfig.java
@@ -1,6 +1,5 @@
package com.baeldung.springcloudgateway.custompredicates.config;
-import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
@@ -24,14 +23,20 @@ public class CustomPredicatesConfig {
public RouteLocator routes(RouteLocatorBuilder builder, GoldenCustomerRoutePredicateFactory gf ) {
return builder.routes()
- .route("dsl_golden_route", r -> r.path("/dsl_api/**")
- .filters(f -> f.stripPrefix(1))
- .uri("https://httpbin.org")
- .predicate(gf.apply(new Config(true, "customerId"))))
- .route("dsl_common_route", r -> r.path("/dsl_api/**")
- .filters(f -> f.stripPrefix(1))
- .uri("https://httpbin.org")
- .predicate(gf.apply(new Config(false, "customerId"))))
+ .route("dsl_golden_route", r ->
+ r.predicate(gf.apply(new Config(true, "customerId")))
+ .and()
+ .path("/dsl_api/**")
+ .filters(f -> f.stripPrefix(1))
+ .uri("https://httpbin.org")
+ )
+ .route("dsl_common_route", r ->
+ r.predicate(gf.apply(new Config(false, "customerId")))
+ .and()
+ .path("/dsl_api/**")
+ .filters(f -> f.stripPrefix(1))
+ .uri("https://httpbin.org")
+ )
.build();
}
diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/backend/QuotesApplication.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/backend/QuotesApplication.java
new file mode 100644
index 0000000000..96daf8a73d
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/backend/QuotesApplication.java
@@ -0,0 +1,44 @@
+/**
+ *
+ */
+package com.baeldung.springcloudgateway.oauth.backend;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.oauth2.server.resource.introspection.NimbusReactiveOpaqueTokenIntrospector;
+import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
+
+import com.baeldung.springcloudgateway.oauth.shared.KeycloakReactiveTokenInstrospector;
+
+/**
+ * @author Philippe
+ *
+ */
+@SpringBootApplication
+@PropertySource("classpath:quotes-application.properties")
+@EnableWebFluxSecurity
+public class QuotesApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(QuotesApplication.class);
+ }
+
+
+ @Bean
+ public ReactiveOpaqueTokenIntrospector keycloakIntrospector(OAuth2ResourceServerProperties props) {
+
+ NimbusReactiveOpaqueTokenIntrospector delegate = new NimbusReactiveOpaqueTokenIntrospector(
+ props.getOpaquetoken().getIntrospectionUri(),
+ props.getOpaquetoken().getClientId(),
+ props.getOpaquetoken().getClientSecret());
+
+ return new KeycloakReactiveTokenInstrospector(delegate);
+ }
+
+
+}
+
diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/backend/domain/Quote.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/backend/domain/Quote.java
new file mode 100644
index 0000000000..042cfa63fa
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/backend/domain/Quote.java
@@ -0,0 +1,35 @@
+package com.baeldung.springcloudgateway.oauth.backend.domain;
+
+
+public class Quote {
+
+ private String symbol;
+ private double price;
+
+ /**
+ * @return the symbol
+ */
+ public String getSymbol() {
+ return symbol;
+ }
+ /**
+ * @param symbol the symbol to set
+ */
+ public void setSymbol(String symbol) {
+ this.symbol = symbol;
+ }
+ /**
+ * @return the price
+ */
+ public double getPrice() {
+ return price;
+ }
+ /**
+ * @param price the price to set
+ */
+ public void setPrice(double price) {
+ this.price = price;
+ }
+
+
+}
diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/backend/web/QuoteApi.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/backend/web/QuoteApi.java
new file mode 100644
index 0000000000..6d3721166c
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/backend/web/QuoteApi.java
@@ -0,0 +1,34 @@
+package com.baeldung.springcloudgateway.oauth.backend.web;
+
+import javax.annotation.PostConstruct;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.baeldung.springcloudgateway.oauth.backend.domain.Quote;
+
+import reactor.core.publisher.Mono;
+
+@RestController
+public class QuoteApi {
+ private static final GrantedAuthority GOLD_CUSTOMER = new SimpleGrantedAuthority("gold");
+
+ @GetMapping("/quotes/{symbol}")
+ public Mono getQuote(@PathVariable("symbol") String symbol, BearerTokenAuthentication auth ) {
+
+ Quote q = new Quote();
+ q.setSymbol(symbol);
+
+ if ( auth.getAuthorities().contains(GOLD_CUSTOMER)) {
+ q.setPrice(10.0);
+ }
+ else {
+ q.setPrice(12.0);
+ }
+ return Mono.just(q);
+ }
+}
diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/server/ResourceServerGatewayApplication.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/server/ResourceServerGatewayApplication.java
new file mode 100644
index 0000000000..1b85867f3b
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/server/ResourceServerGatewayApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.springcloudgateway.oauth.server;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+
+@SpringBootApplication
+@EnableWebFluxSecurity
+public class ResourceServerGatewayApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(ResourceServerGatewayApplication.class,args);
+ }
+}
diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/shared/KeycloakReactiveTokenInstrospector.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/shared/KeycloakReactiveTokenInstrospector.java
new file mode 100644
index 0000000000..e834e6934d
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/oauth/shared/KeycloakReactiveTokenInstrospector.java
@@ -0,0 +1,65 @@
+/**
+ *
+ */
+package com.baeldung.springcloudgateway.oauth.shared;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;
+import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
+import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * Custom ReactiveTokenIntrospector to map realm roles into Spring GrantedAuthorities
+ *
+ */
+public class KeycloakReactiveTokenInstrospector implements ReactiveOpaqueTokenIntrospector {
+
+ private final ReactiveOpaqueTokenIntrospector delegate;
+
+ public KeycloakReactiveTokenInstrospector(ReactiveOpaqueTokenIntrospector delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Mono introspect(String token) {
+
+ return delegate.introspect(token)
+ .map( this::mapPrincipal);
+ }
+
+ protected OAuth2AuthenticatedPrincipal mapPrincipal(OAuth2AuthenticatedPrincipal principal) {
+
+ return new DefaultOAuth2AuthenticatedPrincipal(
+ principal.getName(),
+ principal.getAttributes(),
+ extractAuthorities(principal));
+ }
+
+ protected Collection extractAuthorities(OAuth2AuthenticatedPrincipal principal) {
+
+ //
+ Map> realm_access = principal.getAttribute("realm_access");
+ List roles = realm_access.getOrDefault("roles", Collections.emptyList());
+ List rolesAuthorities = roles.stream()
+ .map(SimpleGrantedAuthority::new)
+ .collect(Collectors.toList());
+
+ Set allAuthorities = new HashSet<>();
+ allAuthorities.addAll(principal.getAuthorities());
+ allAuthorities.addAll(rolesAuthorities);
+
+ return allAuthorities;
+ }
+
+}
diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/rewrite/URLRewriteGatewayApplication.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/rewrite/URLRewriteGatewayApplication.java
new file mode 100644
index 0000000000..46541b4826
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/rewrite/URLRewriteGatewayApplication.java
@@ -0,0 +1,25 @@
+/**
+ *
+ */
+package com.baeldung.springcloudgateway.rewrite;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * @author Baeldung
+ *
+ */
+@SpringBootApplication
+public class URLRewriteGatewayApplication {
+
+ public static void main(String[] args) {
+ new SpringApplicationBuilder(URLRewriteGatewayApplication.class)
+ .profiles("url-rewrite")
+ .run(args);
+ }
+
+}
diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/rewrite/routes/DynamicRewriteRoute.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/rewrite/routes/DynamicRewriteRoute.java
new file mode 100644
index 0000000000..29d40d7021
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/rewrite/routes/DynamicRewriteRoute.java
@@ -0,0 +1,43 @@
+package com.baeldung.springcloudgateway.rewrite.routes;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.gateway.route.RouteLocator;
+import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+
+import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
+import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;
+
+import java.util.Random;
+
+@Configuration
+@Profile("url-rewrite")
+public class DynamicRewriteRoute {
+
+ @Value("${rewrite.backend.uri}")
+ private String backendUri;
+ private static Random rnd = new Random();
+
+ @Bean
+ public RouteLocator dynamicZipCodeRoute(RouteLocatorBuilder builder) {
+ return builder.routes()
+ .route("dynamicRewrite", r ->
+ r.path("/v2/zip/**")
+ .filters(f -> f.filter((exchange, chain) -> {
+ ServerHttpRequest req = exchange.getRequest();
+ addOriginalRequestUrl(exchange, req.getURI());
+ String path = req.getURI().getRawPath();
+ String newPath = path.replaceAll(
+ "/v2/zip/(?.*)",
+ "/api/zip/${zipcode}-" + String.format("%03d", rnd.nextInt(1000)));
+ ServerHttpRequest request = req.mutate().path(newPath).build();
+ exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI());
+ return chain.filter(exchange.mutate().request(request).build());
+ }))
+ .uri(backendUri))
+ .build();
+ }
+}
diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java
index 852e5cadba..9e212cc4bf 100644
--- a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java
+++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java
@@ -8,7 +8,7 @@ public class WebFilterGatewayApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(WebFilterGatewayApplication.class)
- .profiles("webfilters")
+ .profiles("url-rewrite")
.run(args);
}
diff --git a/spring-cloud/spring-cloud-gateway/src/main/resources/application-nosecurity.yml b/spring-cloud/spring-cloud-gateway/src/main/resources/application-nosecurity.yml
new file mode 100644
index 0000000000..40a52ded0f
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/main/resources/application-nosecurity.yml
@@ -0,0 +1,8 @@
+# Enable this profile to disable security
+spring:
+ autoconfigure:
+ exclude:
+ - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
+ - org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfiguration
+ - org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration
+ - org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-gateway/src/main/resources/application-oauth-client.yml b/spring-cloud/spring-cloud-gateway/src/main/resources/application-oauth-client.yml
new file mode 100644
index 0000000000..b097c54eb1
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/main/resources/application-oauth-client.yml
@@ -0,0 +1,26 @@
+server:
+ port: 8087
+spring:
+ cloud:
+ gateway:
+ redis:
+ enabled: false
+ routes:
+ - id: quotes
+ uri: http://localhost:8085
+ predicates:
+ - Path=/quotes/**
+ filters:
- TokenRelay=
+ security:
+ oauth2:
+ client:
provider:
keycloak:
+ issuer-uri: http://localhost:8083/auth/realms/baeldung
+ registration:
quotes-client:
+ provider: keycloak
+ client-id: quotes-client
+ client-secret: 0e082231-a70d-48e8-b8a5-fbfb743041b6
+ scope:
- email
+ - profile
+ - roles
+
+
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-gateway/src/main/resources/application-resource-server.yml b/spring-cloud/spring-cloud-gateway/src/main/resources/application-resource-server.yml
new file mode 100644
index 0000000000..14f713a04a
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/main/resources/application-resource-server.yml
@@ -0,0 +1,19 @@
+server:
+ port: 8086
+spring:
+ security:
+ oauth2:
+ resourceserver:
+ opaquetoken:
+ introspection-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/token/introspect
+ client-id: quotes-client
+ client-secret: 0e082231-a70d-48e8-b8a5-fbfb743041b6
+ cloud:
+ gateway:
+ redis:
+ enabled: false
+ routes:
+ - id: quotes
+ uri: http://localhost:8085
+ predicates:
+ - Path=/quotes/**
diff --git a/spring-cloud/spring-cloud-gateway/src/main/resources/application-url-rewrite.yml b/spring-cloud/spring-cloud-gateway/src/main/resources/application-url-rewrite.yml
new file mode 100644
index 0000000000..b2656151db
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/main/resources/application-url-rewrite.yml
@@ -0,0 +1,11 @@
+spring:
+ cloud:
+ gateway:
+ routes:
+ - id: rewrite_v1
+ uri: ${rewrite.backend.uri:http://example.com}
+ predicates:
+ - Path=/v1/customer/**
+ filters:
+ - RewritePath=/v1/customer/(?.*),/api/$\{segment}
+
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-gateway/src/main/resources/quotes-application.properties b/spring-cloud/spring-cloud-gateway/src/main/resources/quotes-application.properties
new file mode 100644
index 0000000000..48e8999b1b
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/main/resources/quotes-application.properties
@@ -0,0 +1,12 @@
+server.port=8085
+# Disable gateway & redis as we don't need them in this application
+spring.cloud.gateway.enabled=false
+spring.cloud.gateway.redis.enabled=false
+
+# Resource server settings
+spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/token/introspect
+spring.security.oauth2.resourceserver.opaquetoken.client-id=quotes-client
+spring.security.oauth2.resourceserver.opaquetoken.client-secret=0e082231-a70d-48e8-b8a5-fbfb743041b6
+
+
+
diff --git a/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/customfilters/secondservice/SecondServiceIntegrationTest.java b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/customfilters/secondservice/SecondServiceIntegrationTest.java
index 6b2a432d20..f4b3d0f00d 100644
--- a/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/customfilters/secondservice/SecondServiceIntegrationTest.java
+++ b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/customfilters/secondservice/SecondServiceIntegrationTest.java
@@ -2,12 +2,14 @@ package com.baeldung.springcloudgateway.customfilters.secondservice;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import com.baeldung.springcloudgateway.customfilters.secondservice.web.SecondServiceRestController;
-@WebFluxTest(SecondServiceRestController.class)
+@WebFluxTest(controllers = SecondServiceRestController.class,
+ excludeAutoConfiguration = ReactiveSecurityAutoConfiguration.class)
public class SecondServiceIntegrationTest {
@Autowired
diff --git a/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/customfilters/service/ServiceIntegrationTest.java b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/customfilters/service/ServiceIntegrationTest.java
index bfb3f23f0d..9990cd003c 100644
--- a/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/customfilters/service/ServiceIntegrationTest.java
+++ b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/customfilters/service/ServiceIntegrationTest.java
@@ -2,13 +2,15 @@ package com.baeldung.springcloudgateway.customfilters.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.http.HttpHeaders;
import org.springframework.test.web.reactive.server.WebTestClient;
import com.baeldung.springcloudgateway.customfilters.service.web.ServiceRestController;
-@WebFluxTest(ServiceRestController.class)
+@WebFluxTest(controllers = ServiceRestController.class,
+ excludeAutoConfiguration = ReactiveSecurityAutoConfiguration.class)
public class ServiceIntegrationTest {
@Autowired
diff --git a/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/rewrite/URLRewriteGatewayApplicationLiveTest.java b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/rewrite/URLRewriteGatewayApplicationLiveTest.java
new file mode 100644
index 0000000000..41fe37045c
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/rewrite/URLRewriteGatewayApplicationLiveTest.java
@@ -0,0 +1,109 @@
+package com.baeldung.springcloudgateway.rewrite;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+
+import org.junit.AfterClass;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+import com.sun.net.httpserver.HttpServer;
+
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+@ActiveProfiles({ "nosecurity", "url-rewrite" })
+class URLRewriteGatewayApplicationLiveTest {
+
+ // NOTE for Eclipse users: By default, Eclipse will complain about com.sun.** classes.
+ // To solve this issue, follow instructions available at the :
+ // https://stackoverflow.com/questions/13155734/eclipse-cant-recognize-com-sun-net-httpserver-httpserver-package
+ private static HttpServer mockServer;
+ private static Logger log = LoggerFactory.getLogger(URLRewriteGatewayApplicationLiveTest.class);
+
+ // Create a running HttpServer that echoes back the request URL.
+ private static HttpServer startTestServer() {
+
+ try {
+ log.info("[I26] Starting mock server");
+ mockServer = HttpServer.create();
+ mockServer.bind(new InetSocketAddress(0), 0);
+ mockServer.createContext("/api", (xchg) -> {
+ String uri = xchg.getRequestURI()
+ .toString();
+ log.info("[I23] Backend called: uri={}", uri);
+ xchg.getResponseHeaders()
+ .add("Content-Type", "text/plain");
+ xchg.sendResponseHeaders(200, 0);
+ OutputStream os = xchg.getResponseBody();
+ os.write(uri.getBytes());
+ os.flush();
+ os.close();
+ });
+
+ mockServer.start();
+ InetSocketAddress localAddr = mockServer.getAddress();
+ log.info("[I36] mock server started: local address={}", localAddr);
+
+ return mockServer;
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+
+ }
+
+ // TIP: https://www.baeldung.com/spring-dynamicpropertysource
+ @DynamicPropertySource
+ static void registerBackendServer(DynamicPropertyRegistry registry) {
+ registry.add("rewrite.backend.uri", () -> {
+ HttpServer s = startTestServer();
+ return "http://localhost:" + s.getAddress().getPort();
+ });
+ }
+
+ @AfterClass
+ public static void stopMockBackend() throws Exception {
+ log.info("[I40] Shutdown mock http server");
+ mockServer.stop(5);
+ }
+
+ @LocalServerPort
+ private int localPort;
+
+ @Test
+ void testWhenApiCall_thenRewriteSuccess(@Autowired WebTestClient webClient) {
+ webClient.get()
+ .uri("http://localhost:" + localPort + "/v1/customer/customer1")
+ .exchange()
+ .expectBody()
+ .consumeWith((result) -> {
+ String body = new String(result.getResponseBody());
+ log.info("[I99] body={}", body);
+ assertEquals("/api/customer1", body);
+ });
+ }
+
+ @Test
+ void testWhenDslCall_thenRewriteSuccess(@Autowired WebTestClient webClient) {
+ webClient.get()
+ .uri("http://localhost:" + localPort + "/v2/zip/123456")
+ .exchange()
+ .expectBody()
+ .consumeWith((result) -> {
+ String body = new String(result.getResponseBody());
+ log.info("[I99] body={}", body);
+ assertTrue(body.matches("/api/zip/123456-\\d{3}"));
+ });
+ }
+
+}
diff --git a/spring-cloud/spring-cloud-gateway/src/test/postman/OAuth_Gateway.postman_collection.json b/spring-cloud/spring-cloud-gateway/src/test/postman/OAuth_Gateway.postman_collection.json
new file mode 100644
index 0000000000..ac920a271b
--- /dev/null
+++ b/spring-cloud/spring-cloud-gateway/src/test/postman/OAuth_Gateway.postman_collection.json
@@ -0,0 +1,203 @@
+{
+ "info": {
+ "_postman_id": "b3d00e23-c2cd-40ce-a90b-673efb25e5c0",
+ "name": "Baeldung - OAuth",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "item": [
+ {
+ "name": "Token",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "var jsonData = pm.response.json();\r",
+ "pm.environment.set(\"access_token\", jsonData.access_token);\r",
+ "pm.environment.set(\"refresh_token\", jsonData.refresh_token);\r",
+ "pm.environment.set(\"backend_token\", \"Bearer \" + jsonData.access_token);"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "urlencoded",
+ "urlencoded": [
+ {
+ "key": "client_id",
+ "value": "{{client_id}}",
+ "type": "text"
+ },
+ {
+ "key": "client_secret",
+ "value": "{{client_secret}}",
+ "type": "text"
+ },
+ {
+ "key": "grant_type",
+ "value": "password",
+ "type": "text"
+ },
+ {
+ "key": "scope",
+ "value": "email roles profile",
+ "type": "text"
+ },
+ {
+ "key": "username",
+ "value": "maxwell.smart",
+ "type": "text"
+ },
+ {
+ "key": "password",
+ "value": "1234",
+ "type": "text"
+ }
+ ]
+ },
+ "url": {
+ "raw": "{{keycloack_base}}/token",
+ "host": [
+ "{{keycloack_base}}"
+ ],
+ "path": [
+ "token"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Quote",
+ "protocolProfileBehavior": {
+ "disabledSystemHeaders": {
+ "accept": true
+ }
+ },
+ "request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{access_token}}",
+ "type": "string"
+ }
+ ]
+ },
+ "method": "GET",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "http://localhost:8085/quotes/:symbol",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8085",
+ "path": [
+ "quotes",
+ ":symbol"
+ ],
+ "variable": [
+ {
+ "key": "symbol",
+ "value": "IBM"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Quote via Gateway",
+ "protocolProfileBehavior": {
+ "disabledSystemHeaders": {
+ "accept": true
+ }
+ },
+ "request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{access_token}}",
+ "type": "string"
+ }
+ ]
+ },
+ "method": "GET",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "http://localhost:8086/quotes/:symbol",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8086",
+ "path": [
+ "quotes",
+ ":symbol"
+ ],
+ "variable": [
+ {
+ "key": "symbol",
+ "value": "IBM"
+ }
+ ]
+ }
+ },
+ "response": []
+ }
+ ],
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "type": "text/javascript",
+ "exec": [
+ ""
+ ]
+ }
+ },
+ {
+ "listen": "test",
+ "script": {
+ "type": "text/javascript",
+ "exec": [
+ ""
+ ]
+ }
+ }
+ ],
+ "variable": [
+ {
+ "key": "keycloack_base",
+ "value": "http://localhost:8083/auth/realms/baeldung/protocol/openid-connect"
+ },
+ {
+ "key": "client_id",
+ "value": "quotes-client"
+ },
+ {
+ "key": "client_secret",
+ "value": "56be94c8-b20a-4374-899c-e39cb022d3f8"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-kubernetes/pom.xml b/spring-cloud/spring-cloud-kubernetes/pom.xml
index c41c500a1b..698bdce1e5 100644
--- a/spring-cloud/spring-cloud-kubernetes/pom.xml
+++ b/spring-cloud/spring-cloud-kubernetes/pom.xml
@@ -15,6 +15,15 @@
1.0.0-SNAPSHOT
+
+ kubernetes-minikube/demo-frontend
+ kubernetes-minikube/demo-backend
+ kubernetes-selfhealing/liveness-example
+ kubernetes-selfhealing/readiness-example
+ kubernetes-guide/client-service
+ kubernetes-guide/travel-agency-service
+
+
@@ -27,15 +36,6 @@
-
- kubernetes-minikube/demo-frontend
- kubernetes-minikube/demo-backend
- kubernetes-selfhealing/liveness-example
- kubernetes-selfhealing/readiness-example
- kubernetes-guide/client-service
- kubernetes-guide/travel-agency-service
-
-
2021.0.0
diff --git a/spring-cloud/spring-cloud-load-balancer/pom.xml b/spring-cloud/spring-cloud-load-balancer/pom.xml
index 9c650565ed..3b81def641 100644
--- a/spring-cloud/spring-cloud-load-balancer/pom.xml
+++ b/spring-cloud/spring-cloud-load-balancer/pom.xml
@@ -20,6 +20,18 @@
spring-cloud-loadbalancer-client
+
+
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
+
+
+
org.springframework.boot
@@ -32,6 +44,7 @@
2.6.12021.0.0
+ 2.17.1
-
+
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-load-balancer/spring-cloud-loadbalancer-client/pom.xml b/spring-cloud/spring-cloud-load-balancer/spring-cloud-loadbalancer-client/pom.xml
index fc6e2854aa..c141452695 100644
--- a/spring-cloud/spring-cloud-load-balancer/spring-cloud-loadbalancer-client/pom.xml
+++ b/spring-cloud/spring-cloud-load-balancer/spring-cloud-loadbalancer-client/pom.xml
@@ -3,17 +3,30 @@
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
+ com.baeldung.springcloud.loadbalancer
+ spring-cloud-loadbalancer-client
+ 0.0.1-SNAPSHOT
+ spring-cloud-loadbalancer-client
+ Spring Cloud Load Balancer Demo - Client
+
com.baeldung.spring.cloudspring-cloud-loadbalancer1.0.0-SNAPSHOT
- com.baeldung.springcloud.loadbalancer
- spring-cloud-loadbalancer-client
- 0.0.1-SNAPSHOT
- spring-cloud-loadbalancer-client
- Spring Cloud Load Balancer Demo - Client
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
org.springframework.boot
@@ -30,17 +43,6 @@
test
-
-
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${spring-cloud.version}
- pom
- import
-
-
-
@@ -51,4 +53,4 @@
-
+
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-load-balancer/spring-cloud-loadbalancer-server/pom.xml b/spring-cloud/spring-cloud-load-balancer/spring-cloud-loadbalancer-server/pom.xml
index 139996994d..3e61ecc90d 100644
--- a/spring-cloud/spring-cloud-load-balancer/spring-cloud-loadbalancer-server/pom.xml
+++ b/spring-cloud/spring-cloud-load-balancer/spring-cloud-loadbalancer-server/pom.xml
@@ -3,24 +3,23 @@
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
-
- com.baeldung.spring.cloud
- spring-cloud-loadbalancer
- 1.0.0-SNAPSHOT
-
-
com.baeldung.spring.cloud.loadbalancerspring-cloud-loadbalancer-server0.0.1-SNAPSHOTspring-cloud-loadbalancer-serverSpring Cloud Load Balancer Demo - Server
+
+ com.baeldung.spring.cloud
+ spring-cloud-loadbalancer
+ 1.0.0-SNAPSHOT
+
+
org.springframework.bootspring-boot-starter-web
-
org.springframework.bootspring-boot-starter-test
@@ -37,4 +36,4 @@
-
+
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-openfeign/README.md b/spring-cloud/spring-cloud-openfeign/README.md
index 5d3dc060c7..44f4d15b16 100644
--- a/spring-cloud/spring-cloud-openfeign/README.md
+++ b/spring-cloud/spring-cloud-openfeign/README.md
@@ -4,3 +4,4 @@
- [Differences Between Netflix Feign and OpenFeign](https://www.baeldung.com/netflix-feign-vs-openfeign)
- [File Upload With Open Feign](https://www.baeldung.com/java-feign-file-upload)
- [Feign Logging Configuration](https://www.baeldung.com/java-feign-logging)
+- [Provide an OAuth2 Token to a Feign Client](https://www.baeldung.com/spring-cloud-feign-oauth-token)
diff --git a/spring-cloud/spring-cloud-openfeign/pom.xml b/spring-cloud/spring-cloud-openfeign/pom.xml
index 1cb618c7e1..480663eb1c 100644
--- a/spring-cloud/spring-cloud-openfeign/pom.xml
+++ b/spring-cloud/spring-cloud-openfeign/pom.xml
@@ -5,7 +5,6 @@
4.0.0com.baeldung.cloudspring-cloud-openfeign
- 0.0.1-SNAPSHOTspring-cloud-openfeignOpenFeign project for Spring Boot
@@ -49,7 +48,14 @@
io.github.openfeign.formfeign-form-spring
- 3.8.0
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.security
+ spring-security-oauth2-clientorg.springframework.boot
@@ -59,7 +65,7 @@
- Hoxton.SR6
+ 2021.0.0
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/client/PaymentClient.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/client/PaymentClient.java
new file mode 100644
index 0000000000..91932fb7d8
--- /dev/null
+++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/client/PaymentClient.java
@@ -0,0 +1,16 @@
+package com.baeldung.cloud.openfeign.client;
+
+import com.baeldung.cloud.openfeign.model.Payment;
+import com.baeldung.cloud.openfeign.oauthfeign.OAuthFeignConfig;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import java.util.List;
+
+@FeignClient(name = "payment-client", url = "http://localhost:8081/resource-server-jwt", configuration = OAuthFeignConfig.class)
+public interface PaymentClient {
+
+ @RequestMapping(value = "/payments", method = RequestMethod.GET)
+ List getPayments();
+}
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/controller/PaymentController.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/controller/PaymentController.java
new file mode 100644
index 0000000000..97c9437ce1
--- /dev/null
+++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/controller/PaymentController.java
@@ -0,0 +1,24 @@
+package com.baeldung.cloud.openfeign.controller;
+
+import com.baeldung.cloud.openfeign.client.PaymentClient;
+import com.baeldung.cloud.openfeign.model.Payment;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+public class PaymentController {
+
+ private final PaymentClient paymentClient;
+
+ public PaymentController(PaymentClient paymentClient) {
+ this.paymentClient = paymentClient;
+ }
+
+ @GetMapping("/payments")
+ public List getPayments() {
+ List payments = paymentClient.getPayments();
+ return payments;
+ }
+}
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/ExceptionMessage.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/ExceptionMessage.java
new file mode 100644
index 0000000000..45a555b2ea
--- /dev/null
+++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/ExceptionMessage.java
@@ -0,0 +1,55 @@
+package com.baeldung.cloud.openfeign.fileupload.config;
+
+public class ExceptionMessage {
+ private String timestamp;
+ private int status;
+ private String error;
+ private String message;
+ private String path;
+
+ public String getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(String timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ @Override
+ public String toString() {
+ return "ExceptionMessage [timestamp=" + timestamp + ", status=" + status + ", error=" + error + ", message=" + message + ", path=" + path + "]";
+ }
+
+}
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/FeignSupportConfig.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/FeignSupportConfig.java
index 943134213a..802077a3d7 100644
--- a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/FeignSupportConfig.java
+++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/FeignSupportConfig.java
@@ -7,6 +7,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import feign.codec.Encoder;
+import feign.codec.ErrorDecoder;
import feign.form.spring.SpringFormEncoder;
public class FeignSupportConfig {
@@ -19,4 +20,9 @@ public class FeignSupportConfig {
}
}));
}
+
+ @Bean
+ public ErrorDecoder errorDecoder() {
+ return new RetreiveMessageErrorDecoder();
+ }
}
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/RetreiveMessageErrorDecoder.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/RetreiveMessageErrorDecoder.java
new file mode 100644
index 0000000000..09bf8bf54b
--- /dev/null
+++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/RetreiveMessageErrorDecoder.java
@@ -0,0 +1,35 @@
+package com.baeldung.cloud.openfeign.fileupload.config;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.baeldung.cloud.openfeign.exception.BadRequestException;
+import com.baeldung.cloud.openfeign.exception.NotFoundException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import feign.Response;
+import feign.codec.ErrorDecoder;
+
+public class RetreiveMessageErrorDecoder implements ErrorDecoder {
+ private final ErrorDecoder errorDecoder = new Default();
+
+ @Override
+ public Exception decode(String methodKey, Response response) {
+ ExceptionMessage message = null;
+ try (InputStream bodyIs = response.body()
+ .asInputStream()) {
+ ObjectMapper mapper = new ObjectMapper();
+ message = mapper.readValue(bodyIs, ExceptionMessage.class);
+ } catch (IOException e) {
+ return new Exception(e.getMessage());
+ }
+ switch (response.status()) {
+ case 400:
+ return new BadRequestException(message.getMessage() != null ? message.getMessage() : "Bad Request");
+ case 404:
+ return new NotFoundException(message.getMessage() != null ? message.getMessage() : "Not found");
+ default:
+ return errorDecoder.decode(methodKey, response);
+ }
+ }
+}
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/controller/FileController.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/controller/FileController.java
index ebdf7ff6c8..1ddbfcea81 100644
--- a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/controller/FileController.java
+++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/controller/FileController.java
@@ -25,4 +25,9 @@ public class FileController {
return service.uploadFileWithManualClient(file);
}
+ @PostMapping(value = "/upload-error")
+ public String handleFileUploadError(@RequestPart(value = "file") MultipartFile file) {
+ return service.uploadFile(file);
+ }
+
}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadClient.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadClient.java
index 63d17130e9..8f3ef7e421 100644
--- a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadClient.java
+++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadClient.java
@@ -12,4 +12,7 @@ import com.baeldung.cloud.openfeign.fileupload.config.FeignSupportConfig;
public interface UploadClient {
@PostMapping(value = "/upload-file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String fileUpload(@RequestPart(value = "file") MultipartFile file);
+
+ @PostMapping(value = "/upload-file-error", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ String fileUploadError(@RequestPart(value = "file") MultipartFile file);
}
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadService.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadService.java
index 7dd7f5a89c..742a37668b 100644
--- a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadService.java
+++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadService.java
@@ -26,4 +26,8 @@ public class UploadService {
return client.fileUpload(file);
}
+ public String uploadFileError(MultipartFile file) {
+ return client.fileUpload(file);
+ }
+
}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/model/Payment.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/model/Payment.java
new file mode 100644
index 0000000000..c6d45bedbd
--- /dev/null
+++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/model/Payment.java
@@ -0,0 +1,23 @@
+package com.baeldung.cloud.openfeign.model;
+
+public class Payment {
+
+ private String id;
+ private double amount;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public double getAmount() {
+ return amount;
+ }
+
+ public void setAmount(double amount) {
+ this.amount = amount;
+ }
+}
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/oauthfeign/OAuth2WebSecurityConfigurerAdapter.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/oauthfeign/OAuth2WebSecurityConfigurerAdapter.java
new file mode 100644
index 0000000000..af60c3849b
--- /dev/null
+++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/oauthfeign/OAuth2WebSecurityConfigurerAdapter.java
@@ -0,0 +1,19 @@
+package com.baeldung.cloud.openfeign.oauthfeign;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@Configuration
+public class OAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .csrf()
+ .disable()
+ .oauth2Client();
+ http
+ .authorizeRequests().anyRequest().permitAll();
+ }
+}
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/oauthfeign/OAuthClientCredentialsFeignManager.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/oauthfeign/OAuthClientCredentialsFeignManager.java
new file mode 100644
index 0000000000..5e958a93de
--- /dev/null
+++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/oauthfeign/OAuthClientCredentialsFeignManager.java
@@ -0,0 +1,85 @@
+package com.baeldung.cloud.openfeign.oauthfeign;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import static java.util.Objects.isNull;
+
+public class OAuthClientCredentialsFeignManager {
+
+ private static final Logger logger = LoggerFactory.getLogger(OAuthClientCredentialsFeignManager.class);
+
+ private final OAuth2AuthorizedClientManager manager;
+ private final Authentication principal;
+ private final ClientRegistration clientRegistration;
+
+ public OAuthClientCredentialsFeignManager(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) {
+ this.manager = manager;
+ this.clientRegistration = clientRegistration;
+ this.principal = createPrincipal();
+ }
+
+ private Authentication createPrincipal() {
+ return new Authentication() {
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Object getCredentials() {
+ return null;
+ }
+
+ @Override
+ public Object getDetails() {
+ return null;
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return this;
+ }
+
+ @Override
+ public boolean isAuthenticated() {
+ return false;
+ }
+
+ @Override
+ public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
+ }
+
+ @Override
+ public String getName() {
+ return clientRegistration.getClientId();
+ }
+ };
+ }
+
+ public String getAccessToken() {
+ try {
+ OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
+ .withClientRegistrationId(clientRegistration.getRegistrationId())
+ .principal(principal)
+ .build();
+ OAuth2AuthorizedClient client = manager.authorize(oAuth2AuthorizeRequest);
+ if (isNull(client)) {
+ throw new IllegalStateException("client credentials flow on " + clientRegistration.getRegistrationId() + " failed, client is null");
+ }
+ return client.getAccessToken().getTokenValue();
+ } catch (Exception exp) {
+ logger.error("client credentials error " + exp.getMessage());
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/oauthfeign/OAuthFeignConfig.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/oauthfeign/OAuthFeignConfig.java
new file mode 100644
index 0000000000..70ca45d86e
--- /dev/null
+++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/oauthfeign/OAuthFeignConfig.java
@@ -0,0 +1,46 @@
+package com.baeldung.cloud.openfeign.oauthfeign;
+
+import feign.RequestInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.oauth2.client.*;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+
+@Configuration
+public class OAuthFeignConfig {
+
+ public static final String CLIENT_REGISTRATION_ID = "keycloak";
+
+ private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
+ private final ClientRegistrationRepository clientRegistrationRepository;
+
+ public OAuthFeignConfig(OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
+ ClientRegistrationRepository clientRegistrationRepository) {
+ this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
+ this.clientRegistrationRepository = clientRegistrationRepository;
+ }
+
+ @Bean
+ public RequestInterceptor requestInterceptor() {
+ ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId(CLIENT_REGISTRATION_ID);
+ OAuthClientCredentialsFeignManager clientCredentialsFeignManager =
+ new OAuthClientCredentialsFeignManager(authorizedClientManager(), clientRegistration);
+ return requestTemplate -> {
+ requestTemplate.header("Authorization", "Bearer " + clientCredentialsFeignManager.getAccessToken());
+ };
+ }
+
+ @Bean
+ OAuth2AuthorizedClientManager authorizedClientManager() {
+ OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
+ .clientCredentials()
+ .build();
+
+ AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
+ new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientService);
+ authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
+ return authorizedClientManager;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-openfeign/src/main/resources/application.properties b/spring-cloud/spring-cloud-openfeign/src/main/resources/application.properties
index 41bbbde2c3..7188b74c9b 100644
--- a/spring-cloud/spring-cloud-openfeign/src/main/resources/application.properties
+++ b/spring-cloud/spring-cloud-openfeign/src/main/resources/application.properties
@@ -1,3 +1,10 @@
+server.port=8085
+spring.main.allow-bean-definition-overriding=true
spring.application.name= openfeign
logging.level.com.baeldung.cloud.openfeign.client: DEBUG
feign.hystrix.enabled=true
+
+spring.security.oauth2.client.registration.keycloak.authorization-grant-type=client_credentials
+spring.security.oauth2.client.registration.keycloak.client-id=payment-app
+spring.security.oauth2.client.registration.keycloak.client-secret=863e9de4-33d4-4471-b35e-f8d2434385bb
+spring.security.oauth2.client.provider.keycloak.token-uri=http://localhost:8083/auth/realms/master/protocol/openid-connect/token
diff --git a/spring-cloud/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/PaymentClientUnitTest.java b/spring-cloud/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/PaymentClientUnitTest.java
new file mode 100644
index 0000000000..0372728515
--- /dev/null
+++ b/spring-cloud/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/PaymentClientUnitTest.java
@@ -0,0 +1,30 @@
+package com.baeldung.cloud.openfeign;
+
+import com.baeldung.cloud.openfeign.client.PaymentClient;
+import com.baeldung.cloud.openfeign.model.Payment;
+import org.junit.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.junit4.SpringRunner;
+
+import java.util.List;
+
+import static org.junit.Assert.assertFalse;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class PaymentClientUnitTest {
+
+ @Autowired
+ private PaymentClient paymentClient;
+
+ @Test
+ public void whenGetPayment_thenListPayments() {
+
+ List payments = paymentClient.getPayments();
+
+ assertFalse(payments.isEmpty());
+ }
+
+}
diff --git a/spring-cloud/spring-cloud-stream-starters/twitterhdfs/pom.xml b/spring-cloud/spring-cloud-stream-starters/twitterhdfs/pom.xml
index 1c8fa4e694..73d6e62d37 100644
--- a/spring-cloud/spring-cloud-stream-starters/twitterhdfs/pom.xml
+++ b/spring-cloud/spring-cloud-stream-starters/twitterhdfs/pom.xml
@@ -78,6 +78,7 @@
4.13.25.8.1
+ 2.17.1
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-client/pom.xml b/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-client/pom.xml
index 3514924198..3960cfde5d 100644
--- a/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-client/pom.xml
+++ b/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-client/pom.xml
@@ -17,6 +17,13 @@
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+ org.springframework.cloudspring-cloud-starter-parent
@@ -46,4 +53,8 @@
+
+ 2.17.1
+
+
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-server/pom.xml b/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-server/pom.xml
index dcd912df07..c9bc120e4d 100644
--- a/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-server/pom.xml
+++ b/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-server/pom.xml
@@ -17,6 +17,13 @@
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+ org.springframework.cloudspring-cloud-starter-parent
@@ -46,4 +53,8 @@
+
+ 2.17.1
+
+
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-zuul-eureka-integration/zuul-server/pom.xml b/spring-cloud/spring-cloud-zuul-eureka-integration/zuul-server/pom.xml
index bb38ec1351..76d899447f 100644
--- a/spring-cloud/spring-cloud-zuul-eureka-integration/zuul-server/pom.xml
+++ b/spring-cloud/spring-cloud-zuul-eureka-integration/zuul-server/pom.xml
@@ -14,6 +14,13 @@
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+ org.springframework.cloudspring-cloud-starter-parent
@@ -54,4 +61,8 @@
+
+ 2.17.1
+
+
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-zuul/spring-zuul-rate-limiting/pom.xml b/spring-cloud/spring-cloud-zuul/spring-zuul-rate-limiting/pom.xml
index 2969b5eed9..5df22c78c8 100644
--- a/spring-cloud/spring-cloud-zuul/spring-zuul-rate-limiting/pom.xml
+++ b/spring-cloud/spring-cloud-zuul/spring-zuul-rate-limiting/pom.xml
@@ -14,6 +14,18 @@
0.0.1-SNAPSHOT
+
+
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
+
+
+
com.marcosbarbero.cloud
@@ -44,6 +56,7 @@
2.2.0.RELEASE2.4.72020.0.4
+ 2.17.1
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-zuul/spring-zuul-ui/README.md b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/README.md
new file mode 100644
index 0000000000..91a0c5503b
--- /dev/null
+++ b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/README.md
@@ -0,0 +1,3 @@
+### Relevant Articles:
+
+- [Customizing Zuul Exceptions](https://www.baeldung.com/zuul-customize-exception)
diff --git a/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/java/com/baeldung/spring/cloud/zuul/filter/CustomZuulErrorApplication.java b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/java/com/baeldung/spring/cloud/zuul/filter/CustomZuulErrorApplication.java
new file mode 100644
index 0000000000..ae8c3040ef
--- /dev/null
+++ b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/java/com/baeldung/spring/cloud/zuul/filter/CustomZuulErrorApplication.java
@@ -0,0 +1,15 @@
+package com.baeldung.spring.cloud.zuul.filter;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
+
+@EnableZuulProxy
+@SpringBootApplication
+public class CustomZuulErrorApplication extends SpringBootServletInitializer {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CustomZuulErrorApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/java/com/baeldung/spring/cloud/zuul/filter/CustomZuulErrorFilter.java b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/java/com/baeldung/spring/cloud/zuul/filter/CustomZuulErrorFilter.java
new file mode 100644
index 0000000000..d3e52843eb
--- /dev/null
+++ b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/java/com/baeldung/spring/cloud/zuul/filter/CustomZuulErrorFilter.java
@@ -0,0 +1,62 @@
+package com.baeldung.spring.cloud.zuul.filter;
+
+import java.net.ConnectException;
+import java.time.Instant;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import com.netflix.zuul.ZuulFilter;
+import com.netflix.zuul.context.RequestContext;
+import com.netflix.zuul.exception.ZuulException;
+
+@Component
+public class CustomZuulErrorFilter extends ZuulFilter {
+
+ private static final Logger log = LoggerFactory.getLogger(CustomZuulErrorFilter.class);
+
+ private static final String RESPONSE_BODY = "{\n" + " \"timestamp\": " + "\"" + Instant.now()
+ .toString() + "\"" + ",\n" + " \"status\": 503,\n" + " \"error\": \"Service Unavailable\"\n" + "}";
+
+ @Override
+ public Object run() {
+ RequestContext context = RequestContext.getCurrentContext();
+ Throwable throwable = context.getThrowable();
+
+ if (throwable instanceof ZuulException) {
+ final ZuulException zuulException = (ZuulException) throwable;
+ log.error("Zuul exception: " + zuulException.getMessage());
+
+ if (throwable.getCause().getCause().getCause() instanceof ConnectException) {
+
+ // reset throwable to prevent further error handling in follow up filters
+ context.remove("throwable");
+
+ // set custom response attributes
+ context.setResponseBody(RESPONSE_BODY);
+ context.getResponse()
+ .setContentType("application/json");
+ context.setResponseStatusCode(503);
+ }
+
+ }
+ return null;
+ }
+
+ @Override
+ public boolean shouldFilter() {
+ return true;
+ }
+
+ @Override
+ public int filterOrder() {
+ return -1;
+ }
+
+ @Override
+ public String filterType() {
+ return "error";
+ }
+
+}
diff --git a/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/java/com/baeldung/spring/cloud/zuul/filter/ZuulConfiguration.java b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/java/com/baeldung/spring/cloud/zuul/filter/ZuulConfiguration.java
new file mode 100644
index 0000000000..3122862050
--- /dev/null
+++ b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/java/com/baeldung/spring/cloud/zuul/filter/ZuulConfiguration.java
@@ -0,0 +1,98 @@
+package com.baeldung.spring.cloud.zuul.filter;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.cglib.proxy.Callback;
+import org.springframework.cglib.proxy.CallbackFilter;
+import org.springframework.cglib.proxy.Enhancer;
+import org.springframework.cglib.proxy.MethodInterceptor;
+import org.springframework.cglib.proxy.MethodProxy;
+import org.springframework.cglib.proxy.NoOp;
+import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
+import org.springframework.cloud.netflix.zuul.web.ZuulController;
+import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Fix for Zuul configuration with Spring Boot 2.5.x + Zuul - "NoSuchMethodError: ErrorController.getErrorPath()":
+ */
+@Configuration
+public class ZuulConfiguration {
+ /**
+ * The path returned by ErrorController.getErrorPath() with Spring Boot < 2.5
+ * (and no longer available on Spring Boot >= 2.5).
+ */
+ private static final String ERROR_PATH = "/error";
+ private static final String METHOD = "lookupHandler";
+
+ /**
+ * Constructs a new bean post-processor for Zuul.
+ *
+ * @param routeLocator the route locator.
+ * @param zuulController the Zuul controller.
+ * @param errorController the error controller.
+ * @return the new bean post-processor.
+ */
+ @Bean
+ public ZuulPostProcessor zuulPostProcessor(@Autowired RouteLocator routeLocator, @Autowired ZuulController zuulController, @Autowired(required = false) ErrorController errorController) {
+ return new ZuulPostProcessor(routeLocator, zuulController, errorController);
+ }
+
+ private enum LookupHandlerCallbackFilter implements CallbackFilter {
+ INSTANCE;
+
+ @Override
+ public int accept(Method method) {
+ if (METHOD.equals(method.getName())) {
+ return 0;
+ }
+ return 1;
+ }
+ }
+
+ private enum LookupHandlerMethodInterceptor implements MethodInterceptor {
+ INSTANCE;
+
+ @Override
+ public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
+ if (ERROR_PATH.equals(args[0])) {
+ // by entering this branch we avoid the ZuulHandlerMapping.lookupHandler method to trigger the
+ // NoSuchMethodError
+ return null;
+ }
+ return methodProxy.invokeSuper(target, args);
+ }
+ }
+
+ private static final class ZuulPostProcessor implements BeanPostProcessor {
+
+ private final RouteLocator routeLocator;
+ private final ZuulController zuulController;
+ private final boolean hasErrorController;
+
+ ZuulPostProcessor(RouteLocator routeLocator, ZuulController zuulController, ErrorController errorController) {
+ this.routeLocator = routeLocator;
+ this.zuulController = zuulController;
+ this.hasErrorController = (errorController != null);
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ if (hasErrorController && (bean instanceof ZuulHandlerMapping)) {
+ Enhancer enhancer = new Enhancer();
+ enhancer.setSuperclass(ZuulHandlerMapping.class);
+ enhancer.setCallbackFilter(LookupHandlerCallbackFilter.INSTANCE); // only for lookupHandler
+ enhancer.setCallbacks(new Callback[] { LookupHandlerMethodInterceptor.INSTANCE, NoOp.INSTANCE });
+ Constructor> ctor = ZuulHandlerMapping.class.getConstructors()[0];
+ return enhancer.create(ctor.getParameterTypes(), new Object[] { routeLocator, zuulController });
+ }
+ return bean;
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/resources/application.yml b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/resources/application.yml
index 855020a40e..bd517b8a6b 100644
--- a/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/resources/application.yml
+++ b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/main/resources/application.yml
@@ -1,5 +1,8 @@
zuul:
+ SendErrorFilter:
+ post:
+ disable: true
routes:
foos:
path: /foos/**
- url: http://localhost:8081/spring-zuul-foos-resource/foos
+ url: http://localhost:8081/spring-zuul-foos-resource/foos
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/test/java/com/baeldung/spring/cloud/zuul/filter/CustomZuulErrorFilterLiveTest.java b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/test/java/com/baeldung/spring/cloud/zuul/filter/CustomZuulErrorFilterLiveTest.java
new file mode 100644
index 0000000000..da2588966d
--- /dev/null
+++ b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/src/test/java/com/baeldung/spring/cloud/zuul/filter/CustomZuulErrorFilterLiveTest.java
@@ -0,0 +1,24 @@
+package com.baeldung.spring.cloud.zuul.filter;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import io.restassured.RestAssured;
+import io.restassured.response.Response;
+
+public class CustomZuulErrorFilterLiveTest {
+
+ @Test
+ public void whenSendRequestWithoutService_thenCustomError() {
+ final Response response = RestAssured.get("http://localhost:8080/foos/1");
+ assertEquals(503, response.getStatusCode());
+ }
+
+ @Test
+ public void whenSendRequestWithoutCustomErrorFilter_thenError() {
+ final Response response = RestAssured.get("http://localhost:8080/foos/1");
+ assertEquals(500, response.getStatusCode());
+ }
+
+}
diff --git a/spring-di-2/pom.xml b/spring-di-2/pom.xml
index d3be846424..1207506d17 100644
--- a/spring-di-2/pom.xml
+++ b/spring-di-2/pom.xml
@@ -16,6 +16,13 @@
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+ org.springframework.bootspring-boot-dependencies
@@ -80,6 +87,7 @@
2.6.11.111
+ 2.17.1
\ No newline at end of file
diff --git a/spring-di-3/README.md b/spring-di-3/README.md
index 9ab7789f37..4246069616 100644
--- a/spring-di-3/README.md
+++ b/spring-di-3/README.md
@@ -5,4 +5,5 @@ This module contains articles about dependency injection with Spring
### Relevant Articles
- [@Lookup Annotation in Spring](https://www.baeldung.com/spring-lookup)
+- [Spring @Autowired Field Null – Common Causes and Solutions](https://www.baeldung.com/spring-autowired-field-null)
- More articles: [[<-- prev]](../spring-di-2)
diff --git a/spring-di-3/pom.xml b/spring-di-3/pom.xml
index 0d4bbd01af..c6a2687606 100644
--- a/spring-di-3/pom.xml
+++ b/spring-di-3/pom.xml
@@ -16,6 +16,13 @@
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+ org.springframework.bootspring-boot-dependencies
@@ -40,6 +47,7 @@
2.6.1
+ 2.17.1
\ No newline at end of file
diff --git a/spring-di-3/src/main/java/com/baeldung/autowiring/App.java b/spring-di-3/src/main/java/com/baeldung/autowiring/App.java
new file mode 100644
index 0000000000..d2d0db7a60
--- /dev/null
+++ b/spring-di-3/src/main/java/com/baeldung/autowiring/App.java
@@ -0,0 +1,14 @@
+package com.baeldung.autowiring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+public class App {
+
+ public static void main(String[] args) {
+ SpringApplication.run(App.class, args);
+ }
+
+}
diff --git a/spring-di-3/src/main/java/com/baeldung/autowiring/controller/CorrectController.java b/spring-di-3/src/main/java/com/baeldung/autowiring/controller/CorrectController.java
new file mode 100644
index 0000000000..e0c0d7eeac
--- /dev/null
+++ b/spring-di-3/src/main/java/com/baeldung/autowiring/controller/CorrectController.java
@@ -0,0 +1,18 @@
+package com.baeldung.autowiring.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+
+import com.baeldung.autowiring.service.MyService;
+
+@Controller
+public class CorrectController {
+
+ @Autowired
+ MyService myService;
+
+ public String control() {
+ return myService.serve();
+ }
+
+}
diff --git a/spring-di-3/src/main/java/com/baeldung/autowiring/controller/FlawedController.java b/spring-di-3/src/main/java/com/baeldung/autowiring/controller/FlawedController.java
new file mode 100644
index 0000000000..673e686f79
--- /dev/null
+++ b/spring-di-3/src/main/java/com/baeldung/autowiring/controller/FlawedController.java
@@ -0,0 +1,15 @@
+package com.baeldung.autowiring.controller;
+
+import org.springframework.stereotype.Controller;
+
+import com.baeldung.autowiring.service.MyService;
+
+@Controller
+public class FlawedController {
+
+ public String control() {
+ MyService userService = new MyService();
+ return userService.serve();
+ }
+
+}
diff --git a/spring-di-3/src/main/java/com/baeldung/autowiring/service/MyComponent.java b/spring-di-3/src/main/java/com/baeldung/autowiring/service/MyComponent.java
new file mode 100644
index 0000000000..c04ca3f4ba
--- /dev/null
+++ b/spring-di-3/src/main/java/com/baeldung/autowiring/service/MyComponent.java
@@ -0,0 +1,10 @@
+package com.baeldung.autowiring.service;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class MyComponent {
+
+ public void doWork() {}
+
+}
diff --git a/spring-di-3/src/main/java/com/baeldung/autowiring/service/MyService.java b/spring-di-3/src/main/java/com/baeldung/autowiring/service/MyService.java
new file mode 100644
index 0000000000..3443dc05de
--- /dev/null
+++ b/spring-di-3/src/main/java/com/baeldung/autowiring/service/MyService.java
@@ -0,0 +1,19 @@
+package com.baeldung.autowiring.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * The bean corresponding to this class is defined in MyServiceConfiguration
+ * Alternatively, you could choose to decorate this class with @Component or @Service
+ */
+public class MyService {
+
+ @Autowired
+ MyComponent myComponent;
+
+ public String serve() {
+ myComponent.doWork();
+ return "success";
+ }
+
+}
diff --git a/spring-di-3/src/main/java/com/baeldung/autowiring/service/MyServiceConfiguration.java b/spring-di-3/src/main/java/com/baeldung/autowiring/service/MyServiceConfiguration.java
new file mode 100644
index 0000000000..e30e4f770e
--- /dev/null
+++ b/spring-di-3/src/main/java/com/baeldung/autowiring/service/MyServiceConfiguration.java
@@ -0,0 +1,14 @@
+package com.baeldung.autowiring.service;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MyServiceConfiguration {
+
+ @Bean
+ MyService myService() {
+ return new MyService();
+ }
+
+}
diff --git a/spring-di-3/src/test/java/com/baeldung/autowiring/controller/CorrectControllerIntegrationTest.java b/spring-di-3/src/test/java/com/baeldung/autowiring/controller/CorrectControllerIntegrationTest.java
new file mode 100644
index 0000000000..3807641edd
--- /dev/null
+++ b/spring-di-3/src/test/java/com/baeldung/autowiring/controller/CorrectControllerIntegrationTest.java
@@ -0,0 +1,23 @@
+package com.baeldung.autowiring.controller;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest
+public class CorrectControllerIntegrationTest {
+
+ @Autowired
+ CorrectController controller;
+
+ @Test
+ void whenControl_ThenRunSuccessfully() {
+ assertDoesNotThrow(() -> controller.control());
+ }
+
+}
diff --git a/spring-di-3/src/test/java/com/baeldung/autowiring/controller/FlawedControllerIntegrationTest.java b/spring-di-3/src/test/java/com/baeldung/autowiring/controller/FlawedControllerIntegrationTest.java
new file mode 100644
index 0000000000..79d446604f
--- /dev/null
+++ b/spring-di-3/src/test/java/com/baeldung/autowiring/controller/FlawedControllerIntegrationTest.java
@@ -0,0 +1,28 @@
+package com.baeldung.autowiring.controller;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest
+public class FlawedControllerIntegrationTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(FlawedControllerIntegrationTest.class);
+
+ @Autowired
+ FlawedController myController;
+
+ @Test
+ void whenControl_ThenThrowNullPointerException() {
+ NullPointerException npe = assertThrows(NullPointerException.class, () -> myController.control());
+ LOGGER.error("Got a NullPointerException", npe);
+ }
+
+}
diff --git a/spring-ejb/ejb-beans/pom.xml b/spring-ejb/ejb-beans/pom.xml
index 6f20d949b0..37b67beec4 100644
--- a/spring-ejb/ejb-beans/pom.xml
+++ b/spring-ejb/ejb-beans/pom.xml
@@ -38,7 +38,6 @@
tomee-embedded${tomee-embedded.version}
-
org.springframeworkspring-context
diff --git a/spring-ejb/wildfly/pom.xml b/spring-ejb/wildfly/pom.xml
index ae90f71b7c..8a6d41ea82 100644
--- a/spring-ejb/wildfly/pom.xml
+++ b/spring-ejb/wildfly/pom.xml
@@ -21,6 +21,7 @@
wildfly-jpawildfly-ejb-interfaceswildfly-ejb
+ wildfly-mdb
diff --git a/spring-native/pom.xml b/spring-native/pom.xml
index 4455e050f2..f54d7b5308 100644
--- a/spring-native/pom.xml
+++ b/spring-native/pom.xml
@@ -75,6 +75,7 @@
1.81.81.8
+ 2.17.1
\ No newline at end of file
diff --git a/spring-reactive/pom.xml b/spring-reactive/pom.xml
index d31ee04d82..d755c03ae0 100644
--- a/spring-reactive/pom.xml
+++ b/spring-reactive/pom.xml
@@ -1,8 +1,9 @@
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
+ spring-reactivecom.baeldung
@@ -11,8 +12,6 @@
../parent-boot-2
- spring-reactive
-
org.springframework.boot
diff --git a/spring-roo/pom.xml b/spring-roo/pom.xml
index b55a334256..ea42095d92 100644
--- a/spring-roo/pom.xml
+++ b/spring-roo/pom.xml
@@ -625,6 +625,7 @@
1.0.32.0.0.RELEASE1.2.0
+ 2.17.1
\ No newline at end of file
diff --git a/spring-security-modules/spring-5-security-oauth/README.md b/spring-security-modules/spring-5-security-oauth/README.md
index 35e64da639..e5b149fee6 100644
--- a/spring-security-modules/spring-5-security-oauth/README.md
+++ b/spring-security-modules/spring-5-security-oauth/README.md
@@ -8,3 +8,4 @@ This module contains articles about Spring 5 OAuth Security
- [Extracting Principal and Authorities using Spring Security OAuth](https://www.baeldung.com/spring-security-oauth-principal-authorities-extractor)
- [Customizing Authorization and Token Requests with Spring Security 5.1 Client](https://www.baeldung.com/spring-security-custom-oauth-requests)
- [Social Login with Spring Security in a Jersey Application](https://www.baeldung.com/spring-security-social-login-jersey)
+- [Introduction to OAuth2RestTemplate](https://www.baeldung.com/spring-oauth2resttemplate)
diff --git a/spring-security-modules/spring-5-security-oauth/pom.xml b/spring-security-modules/spring-5-security-oauth/pom.xml
index aa4958ae47..8449b01ec0 100644
--- a/spring-security-modules/spring-5-security-oauth/pom.xml
+++ b/spring-security-modules/spring-5-security-oauth/pom.xml
@@ -16,6 +16,18 @@
../../parent-boot-2
+
+
+
+ org.apache.logging.log4j
+ log4j-bom
+ ${log4j2.version}
+ import
+ pom
+
+
+
+
org.springframework.boot
@@ -72,6 +84,7 @@
is available -->
2.5.2com.baeldung.oauth2.SpringOAuthApplication
+ 2.17.1
\ No newline at end of file
diff --git a/spring-security-modules/spring-5-security-oauth/src/main/java/com/baeldung/oauth2resttemplate/AppController.java b/spring-security-modules/spring-5-security-oauth/src/main/java/com/baeldung/oauth2resttemplate/AppController.java
new file mode 100644
index 0000000000..3c3efd950f
--- /dev/null
+++ b/spring-security-modules/spring-5-security-oauth/src/main/java/com/baeldung/oauth2resttemplate/AppController.java
@@ -0,0 +1,32 @@
+package com.baeldung.oauth2resttemplate;
+
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+
+import java.security.Principal;
+import java.util.Collection;
+
+@Controller
+public class AppController {
+
+ OAuth2RestTemplate restTemplate;
+
+ public AppController(OAuth2RestTemplate restTemplate) {
+ this.restTemplate = restTemplate;
+ }
+
+ @GetMapping("/home")
+ public String welcome(Model model, Principal principal) {
+ model.addAttribute("name", principal.getName());
+ return "home";
+ }
+
+ @GetMapping("/repos")
+ public String repos(Model model) {
+ Collection repos = restTemplate.getForObject("https://api.github.com/user/repos", Collection.class);
+ model.addAttribute("repos", repos);
+ return "repositories";
+ }
+}
diff --git a/spring-security-modules/spring-5-security-oauth/src/main/java/com/baeldung/oauth2resttemplate/GithubRepo.java b/spring-security-modules/spring-5-security-oauth/src/main/java/com/baeldung/oauth2resttemplate/GithubRepo.java
new file mode 100644
index 0000000000..48cc05c1de
--- /dev/null
+++ b/spring-security-modules/spring-5-security-oauth/src/main/java/com/baeldung/oauth2resttemplate/GithubRepo.java
@@ -0,0 +1,22 @@
+package com.baeldung.oauth2resttemplate;
+
+public class GithubRepo {
+ 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/spring-security-modules/spring-5-security-oauth/src/main/java/com/baeldung/oauth2resttemplate/SecurityConfig.java b/spring-security-modules/spring-5-security-oauth/src/main/java/com/baeldung/oauth2resttemplate/SecurityConfig.java
new file mode 100644
index 0000000000..fa274d1c9b
--- /dev/null
+++ b/spring-security-modules/spring-5-security-oauth/src/main/java/com/baeldung/oauth2resttemplate/SecurityConfig.java
@@ -0,0 +1,73 @@
+package com.baeldung.oauth2resttemplate;
+
+import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
+import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.oauth2.client.OAuth2ClientContext;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
+import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter;
+import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
+import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+
+import javax.servlet.Filter;
+
+@Configuration
+@EnableOAuth2Client
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+ OAuth2ClientContext oauth2ClientContext;
+
+ public SecurityConfig(OAuth2ClientContext oauth2ClientContext) {
+ this.oauth2ClientContext = oauth2ClientContext;
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.authorizeRequests().antMatchers("/", "/login**", "/error**")
+ .permitAll().anyRequest().authenticated()
+ .and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
+ .and().addFilterBefore(oauth2ClientFilter(), BasicAuthenticationFilter.class);
+ }
+
+ @Bean
+ public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
+ FilterRegistrationBean registration = new FilterRegistrationBean<>();
+ registration.setFilter(filter);
+ registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
+ return registration;
+ }
+
+ @Bean
+ public OAuth2RestTemplate restTemplate() {
+ return new OAuth2RestTemplate(githubClient(), oauth2ClientContext);
+ }
+
+ @Bean
+ @ConfigurationProperties("github.client")
+ public AuthorizationCodeResourceDetails githubClient() {
+ return new AuthorizationCodeResourceDetails();
+ }
+
+ private Filter oauth2ClientFilter() {
+ OAuth2ClientAuthenticationProcessingFilter oauth2ClientFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/github");
+ OAuth2RestTemplate restTemplate = restTemplate();
+ oauth2ClientFilter.setRestTemplate(restTemplate);
+ UserInfoTokenServices tokenServices = new UserInfoTokenServices(githubResource().getUserInfoUri(), githubClient().getClientId());
+ tokenServices.setRestTemplate(restTemplate);
+ oauth2ClientFilter.setTokenServices(tokenServices);
+ return oauth2ClientFilter;
+ }
+
+ @Bean
+ @ConfigurationProperties("github.resource")
+ public ResourceServerProperties githubResource() {
+ return new ResourceServerProperties();
+ }
+}
diff --git a/spring-security-modules/spring-5-security-oauth/src/main/java/com/baeldung/oauth2resttemplate/SpringSecurityOauth2ClientApplication.java b/spring-security-modules/spring-5-security-oauth/src/main/java/com/baeldung/oauth2resttemplate/SpringSecurityOauth2ClientApplication.java
new file mode 100644
index 0000000000..846169e5bf
--- /dev/null
+++ b/spring-security-modules/spring-5-security-oauth/src/main/java/com/baeldung/oauth2resttemplate/SpringSecurityOauth2ClientApplication.java
@@ -0,0 +1,15 @@
+package com.baeldung.oauth2resttemplate;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.PropertySource;
+
+@SpringBootApplication
+@PropertySource("classpath:application-oauth2-rest-template.properties")
+public class SpringSecurityOauth2ClientApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringSecurityOauth2ClientApplication.class, args);
+ }
+
+}
diff --git a/spring-security-modules/spring-5-security-oauth/src/main/resources/application-oauth2-rest-template.properties b/spring-security-modules/spring-5-security-oauth/src/main/resources/application-oauth2-rest-template.properties
new file mode 100644
index 0000000000..15d34b76be
--- /dev/null
+++ b/spring-security-modules/spring-5-security-oauth/src/main/resources/application-oauth2-rest-template.properties
@@ -0,0 +1,9 @@
+github.client.clientId=[CLIENT_ID]
+github.client.clientSecret=[CLIENT_SECRET]
+github.client.userAuthorizationUri=https://github.com/login/oauth/authorize
+github.client.accessTokenUri=https://github.com/login/oauth/access_token
+github.client.clientAuthenticationScheme=form
+
+github.resource.userInfoUri=https://api.github.com/user
+
+spring.thymeleaf.prefix=classpath:/templates/oauth2resttemplate/
\ No newline at end of file
diff --git a/spring-security-modules/spring-5-security-oauth/src/main/resources/templates/oauth2resttemplate/error.html b/spring-security-modules/spring-5-security-oauth/src/main/resources/templates/oauth2resttemplate/error.html
new file mode 100644
index 0000000000..45bcddf654
--- /dev/null
+++ b/spring-security-modules/spring-5-security-oauth/src/main/resources/templates/oauth2resttemplate/error.html
@@ -0,0 +1,9 @@
+
+
+
+ Error
+
+
+
An error occurred.
+
+
\ No newline at end of file
diff --git a/spring-security-modules/spring-5-security-oauth/src/main/resources/templates/oauth2resttemplate/home.html b/spring-security-modules/spring-5-security-oauth/src/main/resources/templates/oauth2resttemplate/home.html
new file mode 100644
index 0000000000..3eba3615d6
--- /dev/null
+++ b/spring-security-modules/spring-5-security-oauth/src/main/resources/templates/oauth2resttemplate/home.html
@@ -0,0 +1,18 @@
+
+
+
+ Home
+
+
+