diff --git a/algorithms-miscellaneous-6/README.md b/algorithms-miscellaneous-6/README.md
new file mode 100644
index 0000000000..99be63d7ca
--- /dev/null
+++ b/algorithms-miscellaneous-6/README.md
@@ -0,0 +1,4 @@
+### Relevant Articles:
+
+- [Boruvka’s Algorithm for Minimum Spanning Trees](https://www.baeldung.com/java-boruvka-algorithm)
+- [Gradient Descent in Java](https://www.baeldung.com/java-gradient-descent)
diff --git a/atomikos/README.md b/atomikos/README.md
new file mode 100644
index 0000000000..19f2e871d4
--- /dev/null
+++ b/atomikos/README.md
@@ -0,0 +1,7 @@
+## Atomikos
+
+This module contains articles about Atomikos
+
+### Relevant Articles:
+
+- [Guide Transactions Using Atomikos]()
diff --git a/atomikos/pom.xml b/atomikos/pom.xml
new file mode 100644
index 0000000000..881adae074
--- /dev/null
+++ b/atomikos/pom.xml
@@ -0,0 +1,119 @@
+
+
+ 4.0.0
+ atomikos
+ atomikos
+
+
+ com.baeldung
+ parent-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+ com.atomikos
+ transactions-jdbc
+ ${atomikos-version}
+
+
+ com.atomikos
+ transactions-jms
+ ${atomikos-version}
+
+
+ com.atomikos
+ transactions-hibernate4
+ ${atomikos-version}
+
+
+ org.springframework
+ spring-context
+ ${spring-version}
+
+
+ org.springframework
+ spring-tx
+ ${spring-version}
+
+
+ org.springframework.data
+ spring-data-jpa
+ 1.11.23.RELEASE
+
+
+ org.springframework
+ spring-test
+ ${spring-version}
+ test
+
+
+ org.hibernate
+ hibernate-entitymanager
+ ${hibernate.version}
+ provided
+
+
+ javax.transaction
+ jta
+
+
+
+
+ org.apache.activemq
+ activemq-core
+ 5.7.0
+
+
+ org.apache.derby
+ derby
+ 10.8.1.2
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
+ javax.transaction
+ jta
+ 1.1
+
+
+ org.apache.geronimo.specs
+ geronimo-jta_1.0.1B_spec
+ 1.0
+
+
+ javax.validation
+ validation-api
+ 2.0.1.Final
+
+
+ org.hibernate.validator
+ hibernate-validator
+ 6.1.2.Final
+
+
+ javax.el
+ javax.el-api
+ 3.0.0
+
+
+ org.glassfish.web
+ javax.el
+ 2.2.4
+
+
+
+
+ 5.0.6
+ 5.1.6.RELEASE
+ 5.4.3.Final
+
+
+
\ No newline at end of file
diff --git a/atomikos/src/main/java/com/baeldung/atomikos/direct/Application.java b/atomikos/src/main/java/com/baeldung/atomikos/direct/Application.java
new file mode 100644
index 0000000000..c51ce70dde
--- /dev/null
+++ b/atomikos/src/main/java/com/baeldung/atomikos/direct/Application.java
@@ -0,0 +1,53 @@
+package com.baeldung.atomikos.direct;
+
+import java.sql.Connection;
+import java.sql.Statement;
+import java.util.UUID;
+
+import javax.sql.DataSource;
+
+import com.atomikos.icatch.jta.UserTransactionImp;
+
+public class Application {
+
+ private DataSource inventoryDataSource;
+ private DataSource orderDataSource;
+
+ public Application(DataSource inventoryDataSource, DataSource orderDataSource) {
+ this.inventoryDataSource = inventoryDataSource;
+ this.orderDataSource = orderDataSource;
+ }
+
+ public void placeOrder(String productId, int amount) throws Exception {
+
+ UserTransactionImp utx = new UserTransactionImp();
+ String orderId = UUID.randomUUID()
+ .toString();
+ boolean rollback = false;
+ try {
+ utx.begin();
+ Connection inventoryConnection = inventoryDataSource.getConnection();
+ Connection orderConnection = orderDataSource.getConnection();
+ Statement s1 = inventoryConnection.createStatement();
+ String q1 = "update Inventory set balance = balance - " + amount + " where productId ='" + productId + "'";
+ s1.executeUpdate(q1);
+ s1.close();
+ Statement s2 = orderConnection.createStatement();
+ String q2 = "insert into Orders values ( '" + orderId + "', '" + productId + "', " + amount + " )";
+ s2.executeUpdate(q2);
+ s2.close();
+ inventoryConnection.close();
+ orderConnection.close();
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ rollback = true;
+ } finally {
+ if (!rollback)
+ utx.commit();
+ else
+ utx.rollback();
+ }
+
+ }
+
+}
diff --git a/atomikos/src/main/java/com/baeldung/atomikos/spring/Application.java b/atomikos/src/main/java/com/baeldung/atomikos/spring/Application.java
new file mode 100644
index 0000000000..b480e68d8d
--- /dev/null
+++ b/atomikos/src/main/java/com/baeldung/atomikos/spring/Application.java
@@ -0,0 +1,41 @@
+package com.baeldung.atomikos.spring;
+
+import java.sql.Connection;
+import java.sql.Statement;
+import java.util.UUID;
+
+import javax.sql.DataSource;
+
+import org.springframework.transaction.annotation.Transactional;
+
+public class Application {
+
+ private DataSource inventoryDataSource;
+ private DataSource orderDataSource;
+
+ public Application(DataSource inventoryDataSource, DataSource orderDataSource) {
+ this.inventoryDataSource = inventoryDataSource;
+ this.orderDataSource = orderDataSource;
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ public void placeOrder(String productId, int amount) throws Exception {
+
+ String orderId = UUID.randomUUID()
+ .toString();
+ Connection inventoryConnection = inventoryDataSource.getConnection();
+ Connection orderConnection = orderDataSource.getConnection();
+ Statement s1 = inventoryConnection.createStatement();
+ String q1 = "update Inventory set balance = balance - " + amount + " where productId ='" + productId + "'";
+ s1.executeUpdate(q1);
+ s1.close();
+ Statement s2 = orderConnection.createStatement();
+ String q2 = "insert into Orders values ( '" + orderId + "', '" + productId + "', " + amount + " )";
+ s2.executeUpdate(q2);
+ s2.close();
+ inventoryConnection.close();
+ orderConnection.close();
+
+ }
+
+}
diff --git a/atomikos/src/main/java/com/baeldung/atomikos/spring/config/Config.java b/atomikos/src/main/java/com/baeldung/atomikos/spring/config/Config.java
new file mode 100644
index 0000000000..c6ef83c4ca
--- /dev/null
+++ b/atomikos/src/main/java/com/baeldung/atomikos/spring/config/Config.java
@@ -0,0 +1,68 @@
+package com.baeldung.atomikos.spring.config;
+
+import java.util.Properties;
+
+import javax.transaction.SystemException;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.transaction.jta.JtaTransactionManager;
+
+import com.atomikos.icatch.jta.UserTransactionManager;
+import com.atomikos.jdbc.AtomikosDataSourceBean;
+import com.baeldung.atomikos.spring.Application;
+
+@Configuration
+@EnableTransactionManagement
+public class Config {
+
+ @Bean(initMethod = "init", destroyMethod = "close")
+ public AtomikosDataSourceBean inventoryDataSource() {
+ AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
+ dataSource.setLocalTransactionMode(true);
+ dataSource.setUniqueResourceName("db1");
+ dataSource.setXaDataSourceClassName("org.apache.derby.jdbc.EmbeddedXADataSource");
+ Properties xaProperties = new Properties();
+ xaProperties.put("databaseName", "db1");
+ xaProperties.put("createDatabase", "create");
+ dataSource.setXaProperties(xaProperties);
+ dataSource.setPoolSize(10);
+ return dataSource;
+ }
+
+ @Bean(initMethod = "init", destroyMethod = "close")
+ public AtomikosDataSourceBean orderDataSource() {
+ AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
+ dataSource.setLocalTransactionMode(true);
+ dataSource.setUniqueResourceName("db2");
+ dataSource.setXaDataSourceClassName("org.apache.derby.jdbc.EmbeddedXADataSource");
+ Properties xaProperties = new Properties();
+ xaProperties.put("databaseName", "db2");
+ xaProperties.put("createDatabase", "create");
+ dataSource.setXaProperties(xaProperties);
+ dataSource.setPoolSize(10);
+ return dataSource;
+ }
+
+ @Bean(initMethod = "init", destroyMethod = "close")
+ public UserTransactionManager userTransactionManager() throws SystemException {
+ UserTransactionManager userTransactionManager = new UserTransactionManager();
+ userTransactionManager.setTransactionTimeout(300);
+ userTransactionManager.setForceShutdown(true);
+ return userTransactionManager;
+ }
+
+ @Bean
+ public JtaTransactionManager jtaTransactionManager() throws SystemException {
+ JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
+ jtaTransactionManager.setTransactionManager(userTransactionManager());
+ jtaTransactionManager.setUserTransaction(userTransactionManager());
+ return jtaTransactionManager;
+ }
+
+ @Bean
+ public Application application() {
+ return new Application(inventoryDataSource(), orderDataSource());
+ }
+}
\ No newline at end of file
diff --git a/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/Application.java b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/Application.java
new file mode 100644
index 0000000000..cf1fef2cd8
--- /dev/null
+++ b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/Application.java
@@ -0,0 +1,48 @@
+package com.baeldung.atomikos.spring.jpa;
+
+import java.util.Set;
+import java.util.UUID;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.baeldung.atomikos.spring.jpa.inventory.Inventory;
+import com.baeldung.atomikos.spring.jpa.inventory.InventoryRepository;
+import com.baeldung.atomikos.spring.jpa.order.Order;
+import com.baeldung.atomikos.spring.jpa.order.OrderRepository;
+
+public class Application {
+
+ @Autowired
+ private InventoryRepository inventoryRepository;
+
+ @Autowired
+ private OrderRepository orderRepository;
+
+ @Transactional(rollbackFor = Exception.class)
+ public void placeOrder(String productId, int amount) throws Exception {
+
+ String orderId = UUID.randomUUID()
+ .toString();
+ Inventory inventory = inventoryRepository.findOne(productId);
+ inventory.setBalance(inventory.getBalance() - amount);
+ inventoryRepository.save(inventory);
+ Order order = new Order();
+ order.setOrderId(orderId);
+ order.setProductId(productId);
+ order.setAmount(new Long(amount));
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ Validator validator = factory.getValidator();
+ Set> violations = validator.validate(order);
+ if (violations.size() > 0)
+ throw new Exception("Invalid instance of an order.");
+ orderRepository.save(order);
+
+ }
+
+}
diff --git a/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/config/Config.java b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/config/Config.java
new file mode 100644
index 0000000000..6716f19576
--- /dev/null
+++ b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/config/Config.java
@@ -0,0 +1,38 @@
+package com.baeldung.atomikos.spring.jpa.config;
+
+import javax.transaction.SystemException;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.transaction.jta.JtaTransactionManager;
+
+import com.atomikos.icatch.jta.UserTransactionManager;
+import com.baeldung.atomikos.spring.jpa.Application;
+
+@Configuration
+@EnableTransactionManagement
+public class Config {
+
+ @Bean(initMethod = "init", destroyMethod = "close")
+ public UserTransactionManager userTransactionManager() throws SystemException {
+ UserTransactionManager userTransactionManager = new UserTransactionManager();
+ userTransactionManager.setTransactionTimeout(300);
+ userTransactionManager.setForceShutdown(true);
+ return userTransactionManager;
+ }
+
+ @Bean
+ public JtaTransactionManager transactionManager() throws SystemException {
+ JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
+ jtaTransactionManager.setTransactionManager(userTransactionManager());
+ jtaTransactionManager.setUserTransaction(userTransactionManager());
+ return jtaTransactionManager;
+ }
+
+ @Bean
+ public Application application() {
+ return new Application();
+ }
+
+}
\ No newline at end of file
diff --git a/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/inventory/Inventory.java b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/inventory/Inventory.java
new file mode 100644
index 0000000000..999879218c
--- /dev/null
+++ b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/inventory/Inventory.java
@@ -0,0 +1,31 @@
+package com.baeldung.atomikos.spring.jpa.inventory;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "INVENTORY")
+public class Inventory {
+
+ @Id
+ private String productId;
+ private Long balance;
+
+ public String getProductId() {
+ return productId;
+ }
+
+ public void setProductId(String productId) {
+ this.productId = productId;
+ }
+
+ public Long getBalance() {
+ return balance;
+ }
+
+ public void setBalance(Long balance) {
+ this.balance = balance;
+ }
+
+}
diff --git a/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/inventory/InventoryConfig.java b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/inventory/InventoryConfig.java
new file mode 100644
index 0000000000..5301ad6ff2
--- /dev/null
+++ b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/inventory/InventoryConfig.java
@@ -0,0 +1,53 @@
+package com.baeldung.atomikos.spring.jpa.inventory;
+
+import java.util.Properties;
+
+import javax.persistence.EntityManagerFactory;
+
+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.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
+
+import com.atomikos.jdbc.AtomikosDataSourceBean;
+
+@Configuration
+@EnableJpaRepositories(basePackages = "com.baeldung.atomikos.spring.jpa.inventory", entityManagerFactoryRef = "inventoryEntityManager", transactionManagerRef = "transactionManager")
+public class InventoryConfig {
+
+ @Bean(initMethod = "init", destroyMethod = "close")
+ public AtomikosDataSourceBean inventoryDataSource() {
+ AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
+ dataSource.setLocalTransactionMode(true);
+ dataSource.setUniqueResourceName("db1");
+ dataSource.setXaDataSourceClassName("org.apache.derby.jdbc.EmbeddedXADataSource");
+ Properties xaProperties = new Properties();
+ xaProperties.put("databaseName", "db1");
+ xaProperties.put("createDatabase", "create");
+ dataSource.setXaProperties(xaProperties);
+ dataSource.setPoolSize(10);
+ return dataSource;
+ }
+
+ @Bean
+ public EntityManagerFactory inventoryEntityManager() {
+ HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
+ LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
+ factory.setJpaVendorAdapter(vendorAdapter);
+ factory.setPackagesToScan("com.baeldung.atomikos.spring.jpa.inventory");
+ factory.setDataSource(inventoryDataSource());
+ Properties jpaProperties = new Properties();
+ //jpaProperties.put("hibernate.show_sql", "true");
+ //jpaProperties.put("hibernate.format_sql", "true");
+ jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyDialect");
+ jpaProperties.put("hibernate.current_session_context_class", "jta");
+ jpaProperties.put("javax.persistence.transactionType", "jta");
+ jpaProperties.put("hibernate.transaction.manager_lookup_class", "com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup");
+ jpaProperties.put("hibernate.hbm2ddl.auto", "create-drop");
+ factory.setJpaProperties(jpaProperties);
+ factory.afterPropertiesSet();
+ return factory.getObject();
+ }
+
+}
\ No newline at end of file
diff --git a/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/inventory/InventoryRepository.java b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/inventory/InventoryRepository.java
new file mode 100644
index 0000000000..c3868e51bf
--- /dev/null
+++ b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/inventory/InventoryRepository.java
@@ -0,0 +1,9 @@
+package com.baeldung.atomikos.spring.jpa.inventory;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface InventoryRepository extends JpaRepository {
+
+}
diff --git a/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/order/Order.java b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/order/Order.java
new file mode 100644
index 0000000000..4b9ae2dd1d
--- /dev/null
+++ b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/order/Order.java
@@ -0,0 +1,42 @@
+package com.baeldung.atomikos.spring.jpa.order;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.validation.constraints.Max;
+
+@Entity
+@Table(name = "ORDERS")
+public class Order {
+
+ @Id
+ private String orderId;
+ private String productId;
+ @Max(5)
+ private Long amount;
+
+ public String getOrderId() {
+ return orderId;
+ }
+
+ public void setOrderId(String orderId) {
+ this.orderId = orderId;
+ }
+
+ public String getProductId() {
+ return productId;
+ }
+
+ public void setProductId(String productId) {
+ this.productId = productId;
+ }
+
+ public Long getAmount() {
+ return amount;
+ }
+
+ public void setAmount(Long amount) {
+ this.amount = amount;
+ }
+
+}
diff --git a/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/order/OrderConfig.java b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/order/OrderConfig.java
new file mode 100644
index 0000000000..b4274bb64c
--- /dev/null
+++ b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/order/OrderConfig.java
@@ -0,0 +1,53 @@
+package com.baeldung.atomikos.spring.jpa.order;
+
+import java.util.Properties;
+
+import javax.persistence.EntityManagerFactory;
+
+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.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
+
+import com.atomikos.jdbc.AtomikosDataSourceBean;
+
+@Configuration
+@EnableJpaRepositories(basePackages = "com.baeldung.atomikos.spring.jpa.order", entityManagerFactoryRef = "orderEntityManager", transactionManagerRef = "transactionManager")
+public class OrderConfig {
+
+ @Bean(initMethod = "init", destroyMethod = "close")
+ public AtomikosDataSourceBean orderDataSource() {
+ AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
+ dataSource.setLocalTransactionMode(true);
+ dataSource.setUniqueResourceName("db2");
+ dataSource.setXaDataSourceClassName("org.apache.derby.jdbc.EmbeddedXADataSource");
+ Properties xaProperties = new Properties();
+ xaProperties.put("databaseName", "db2");
+ xaProperties.put("createDatabase", "create");
+ dataSource.setXaProperties(xaProperties);
+ dataSource.setPoolSize(10);
+ return dataSource;
+ }
+
+ @Bean
+ public EntityManagerFactory orderEntityManager() {
+ HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
+ LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
+ factory.setJpaVendorAdapter(vendorAdapter);
+ factory.setPackagesToScan("com.baeldung.atomikos.spring.jpa.order");
+ factory.setDataSource(orderDataSource());
+ Properties jpaProperties = new Properties();
+ //jpaProperties.put("hibernate.show_sql", "true");
+ //jpaProperties.put("hibernate.format_sql", "true");
+ jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyDialect");
+ jpaProperties.put("hibernate.current_session_context_class", "jta");
+ jpaProperties.put("javax.persistence.transactionType", "jta");
+ jpaProperties.put("hibernate.transaction.manager_lookup_class", "com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup");
+ jpaProperties.put("hibernate.hbm2ddl.auto", "create-drop");
+ factory.setJpaProperties(jpaProperties);
+ factory.afterPropertiesSet();
+ return factory.getObject();
+ }
+
+}
\ No newline at end of file
diff --git a/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/order/OrderRepository.java b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/order/OrderRepository.java
new file mode 100644
index 0000000000..2d5610ebca
--- /dev/null
+++ b/atomikos/src/main/java/com/baeldung/atomikos/spring/jpa/order/OrderRepository.java
@@ -0,0 +1,9 @@
+package com.baeldung.atomikos.spring.jpa.order;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface OrderRepository extends JpaRepository {
+
+}
diff --git a/atomikos/src/main/resources/logback.xml b/atomikos/src/main/resources/logback.xml
new file mode 100644
index 0000000000..7d900d8ea8
--- /dev/null
+++ b/atomikos/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/atomikos/src/main/resources/schema.sql b/atomikos/src/main/resources/schema.sql
new file mode 100644
index 0000000000..5136ad1284
--- /dev/null
+++ b/atomikos/src/main/resources/schema.sql
@@ -0,0 +1,10 @@
+CREATE TABLE INVENTORY (
+ productId VARCHAR PRIMARY KEY,
+ balance INT
+);
+
+CREATE TABLE ORDERS (
+ orderId VARCHAR PRIMARY KEY,
+ productId VARCHAR,
+ amount INT NOT NULL CHECK (amount <= 5)
+);
\ No newline at end of file
diff --git a/atomikos/src/main/resources/transactions.properties b/atomikos/src/main/resources/transactions.properties
new file mode 100644
index 0000000000..8e027032fa
--- /dev/null
+++ b/atomikos/src/main/resources/transactions.properties
@@ -0,0 +1 @@
+com.atomikos.icatch.file=logs
\ No newline at end of file
diff --git a/atomikos/src/test/java/com/baeldung/atomikos/direct/ApplicationUnitTest.java b/atomikos/src/test/java/com/baeldung/atomikos/direct/ApplicationUnitTest.java
new file mode 100644
index 0000000000..1a467807ba
--- /dev/null
+++ b/atomikos/src/test/java/com/baeldung/atomikos/direct/ApplicationUnitTest.java
@@ -0,0 +1,118 @@
+package com.baeldung.atomikos.direct;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Properties;
+import java.util.UUID;
+
+import javax.sql.DataSource;
+
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.atomikos.icatch.jta.UserTransactionImp;
+import com.atomikos.jdbc.AtomikosDataSourceBean;
+
+public class ApplicationUnitTest {
+
+ private static DataSource inventoryDataSource;
+ private static DataSource orderDataSource;
+
+ private static String productId = UUID.randomUUID()
+ .toString();
+
+ @Test
+ @Ignore
+ public void testPlaceOrderSuccess() throws Exception {
+ int amount = 1;
+ long initialBalance = getBalance(inventoryDataSource, productId);
+ Application application = new Application(inventoryDataSource, orderDataSource);
+ application.placeOrder(productId, amount);
+ long finalBalance = getBalance(inventoryDataSource, productId);
+ assertEquals(initialBalance - amount, finalBalance);
+ }
+
+ @Test
+ @Ignore
+ public void testPlaceOrderFailure() throws Exception {
+ int amount = 10;
+ long initialBalance = getBalance(inventoryDataSource, productId);
+ Application application = new Application(inventoryDataSource, orderDataSource);
+ application.placeOrder(productId, amount);
+ long finalBalance = getBalance(inventoryDataSource, productId);
+ assertEquals(initialBalance, finalBalance);
+ }
+
+ @BeforeClass
+ public static void setUp() throws SQLException {
+
+ inventoryDataSource = getDataSource("db1");
+ orderDataSource = getDataSource("db2");
+ Connection inventoryConnection = inventoryDataSource.getConnection();
+ Connection orderConnection = orderDataSource.getConnection();
+ String createInventoryTable = "create table Inventory ( " + " productId VARCHAR ( 100 ) PRIMARY KEY, balance INT )";
+ String createInventoryRow = "insert into Inventory values ( '" + productId + "', 10000 )";
+ Statement s1 = inventoryConnection.createStatement();
+ try {
+ s1.executeUpdate(createInventoryTable);
+ } catch (Exception e) {
+ System.out.println("Inventory table exists");
+ }
+ try {
+ s1.executeUpdate(createInventoryRow);
+ } catch (Exception e) {
+ System.out.println("Product row exists");
+ }
+ s1.close();
+ String createOrderTable = "create table Orders ( orderId VARCHAR ( 100 ) PRIMARY KEY, productId VARCHAR ( 100 ), amount INT NOT NULL CHECK (amount <= 5) )";
+ Statement s2 = orderConnection.createStatement();
+ try {
+ s2.executeUpdate(createOrderTable);
+ } catch (Exception e) {
+ System.out.println("Orders table exists");
+ }
+ s2.close();
+ inventoryConnection.close();
+ orderConnection.close();
+ }
+
+ private static DataSource getDataSource(String db) {
+
+ DataSource ds;
+ AtomikosDataSourceBean ads = new AtomikosDataSourceBean();
+ ads.setXaDataSourceClassName("org.apache.derby.jdbc.EmbeddedXADataSource");
+ Properties properties = new Properties();
+ properties.put("databaseName", db);
+ properties.put("createDatabase", "create");
+ ads.setXaProperties(properties);
+ ads.setUniqueResourceName(db);
+ ads.setPoolSize(10); // optional
+ ads.setBorrowConnectionTimeout(10); // optional
+ ds = ads;
+ return ds;
+
+ }
+
+ private static long getBalance(DataSource inventoryDataSource, String productId) throws Exception {
+
+ UserTransactionImp utx = new UserTransactionImp();
+ utx.begin();
+ Connection inventoryConnection = inventoryDataSource.getConnection();
+ Statement s1 = inventoryConnection.createStatement();
+ String q1 = "select balance from Inventory where productId='" + productId + "'";
+ ResultSet rs1 = s1.executeQuery(q1);
+ if (rs1 == null || !rs1.next())
+ throw new Exception("Product not found: " + productId);
+ long balance = rs1.getLong(1);
+ inventoryConnection.close();
+ utx.commit();
+ return balance;
+
+ }
+
+}
diff --git a/atomikos/src/test/java/com/baeldung/atomikos/spring/ApplicationUnitTest.java b/atomikos/src/test/java/com/baeldung/atomikos/spring/ApplicationUnitTest.java
new file mode 100644
index 0000000000..0c9392eac4
--- /dev/null
+++ b/atomikos/src/test/java/com/baeldung/atomikos/spring/ApplicationUnitTest.java
@@ -0,0 +1,108 @@
+package com.baeldung.atomikos.spring;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.UUID;
+
+import javax.sql.DataSource;
+
+import org.junit.Before;
+import org.junit.Ignore;
+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 com.baeldung.atomikos.spring.config.Config;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = { Config.class })
+public class ApplicationUnitTest {
+
+ private static String productId = UUID.randomUUID()
+ .toString();
+
+ @Autowired
+ Application application;
+
+ @Autowired
+ DataSource inventoryDataSource;
+
+ @Autowired
+ DataSource orderDataSource;
+
+ @Test
+ @Ignore
+ public void testPlaceOrderSuccess() throws Exception {
+ int amount = 1;
+ long initialBalance = getBalance(inventoryDataSource, productId);
+ application.placeOrder(productId, amount);
+ long finalBalance = getBalance(inventoryDataSource, productId);
+ assertEquals(initialBalance - amount, finalBalance);
+ }
+
+ @Test
+ @Ignore
+ public void testPlaceOrderFailure() throws Exception {
+ int amount = 10;
+ long initialBalance = getBalance(inventoryDataSource, productId);
+ try {
+ application.placeOrder(productId, amount);
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ }
+ long finalBalance = getBalance(inventoryDataSource, productId);
+ assertEquals(initialBalance, finalBalance);
+ }
+
+ @Before
+ public void setUp() throws SQLException {
+
+ Connection inventoryConnection = inventoryDataSource.getConnection();
+ Connection orderConnection = orderDataSource.getConnection();
+ String createInventoryTable = "create table Inventory ( " + " productId VARCHAR ( 100 ) PRIMARY KEY, balance INT )";
+ String createInventoryRow = "insert into Inventory values ( '" + productId + "', 10000 )";
+ Statement s1 = inventoryConnection.createStatement();
+ try {
+ s1.executeUpdate(createInventoryTable);
+ } catch (Exception e) {
+ System.out.println("Inventory table exists");
+ }
+ try {
+ s1.executeUpdate(createInventoryRow);
+ } catch (Exception e) {
+ System.out.println("Product row exists");
+ }
+ s1.close();
+ String createOrderTable = "create table Orders ( orderId VARCHAR ( 100 ) PRIMARY KEY, productId VARCHAR ( 100 ), amount INT NOT NULL CHECK (amount <= 5) )";
+ Statement s2 = orderConnection.createStatement();
+ try {
+ s2.executeUpdate(createOrderTable);
+ } catch (Exception e) {
+ System.out.println("Orders table exists");
+ }
+ s2.close();
+ inventoryConnection.close();
+ orderConnection.close();
+ }
+
+ private static long getBalance(DataSource inventoryDataSource, String productId) throws Exception {
+
+ Connection inventoryConnection = inventoryDataSource.getConnection();
+ Statement s1 = inventoryConnection.createStatement();
+ String q1 = "select balance from Inventory where productId='" + productId + "'";
+ ResultSet rs1 = s1.executeQuery(q1);
+ if (rs1 == null || !rs1.next())
+ throw new Exception("Product not found: " + productId);
+ long balance = rs1.getLong(1);
+ inventoryConnection.close();
+ return balance;
+
+ }
+
+}
diff --git a/atomikos/src/test/java/com/baeldung/atomikos/spring/jpa/ApplicationUnitTest.java b/atomikos/src/test/java/com/baeldung/atomikos/spring/jpa/ApplicationUnitTest.java
new file mode 100644
index 0000000000..e6a3c1982c
--- /dev/null
+++ b/atomikos/src/test/java/com/baeldung/atomikos/spring/jpa/ApplicationUnitTest.java
@@ -0,0 +1,80 @@
+package com.baeldung.atomikos.spring.jpa;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.junit.Before;
+import org.junit.Ignore;
+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 com.baeldung.atomikos.spring.jpa.config.Config;
+import com.baeldung.atomikos.spring.jpa.inventory.Inventory;
+import com.baeldung.atomikos.spring.jpa.inventory.InventoryConfig;
+import com.baeldung.atomikos.spring.jpa.inventory.InventoryRepository;
+import com.baeldung.atomikos.spring.jpa.order.OrderConfig;
+import com.baeldung.atomikos.spring.jpa.order.OrderRepository;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = { Config.class, InventoryConfig.class, OrderConfig.class })
+public class ApplicationUnitTest {
+
+ private static String productId = UUID.randomUUID()
+ .toString();
+
+ @Autowired
+ Application application;
+
+ @Autowired
+ InventoryRepository inventoryRepository;
+
+ @Autowired
+ OrderRepository orderRepository;
+
+ @Test
+ @Ignore
+ public void testPlaceOrderSuccess() throws Exception {
+ int amount = 1;
+ long initialBalance = getBalance(inventoryRepository, productId);
+ application.placeOrder(productId, amount);
+ long finalBalance = getBalance(inventoryRepository, productId);
+ assertEquals(initialBalance - amount, finalBalance);
+ }
+
+ @Test
+ @Ignore
+ public void testPlaceOrderFailure() throws Exception {
+ int amount = 10;
+ long initialBalance = getBalance(inventoryRepository, productId);
+ try {
+ application.placeOrder(productId, amount);
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ }
+ long finalBalance = getBalance(inventoryRepository, productId);
+ assertEquals(initialBalance, finalBalance);
+ }
+
+ @Before
+ public void setUp() throws SQLException {
+
+ Inventory inventory = new Inventory();
+ inventory.setProductId(productId);
+ inventory.setBalance(new Long(10000));
+ inventoryRepository.save(inventory);
+
+ }
+
+ private static long getBalance(InventoryRepository inventoryRepository, String productId) throws Exception {
+
+ return inventoryRepository.findOne(productId)
+ .getBalance();
+
+ }
+
+}
diff --git a/atomikos/src/test/resources/logback.xml b/atomikos/src/test/resources/logback.xml
new file mode 100644
index 0000000000..7d900d8ea8
--- /dev/null
+++ b/atomikos/src/test/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/atomikos/src/test/resources/transactions.properties b/atomikos/src/test/resources/transactions.properties
new file mode 100644
index 0000000000..8e027032fa
--- /dev/null
+++ b/atomikos/src/test/resources/transactions.properties
@@ -0,0 +1 @@
+com.atomikos.icatch.file=logs
\ No newline at end of file
diff --git a/core-groovy/src/test/groovy/com/baeldung/stringtoint/ConvertStringToInt.groovy b/core-groovy/src/test/groovy/com/baeldung/stringtoint/ConvertStringToInt.groovy
new file mode 100644
index 0000000000..48cf48fa8a
--- /dev/null
+++ b/core-groovy/src/test/groovy/com/baeldung/stringtoint/ConvertStringToInt.groovy
@@ -0,0 +1,110 @@
+package com.baeldung.stringtoint
+
+import org.junit.Test
+
+import java.text.DecimalFormat
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNull
+
+class ConvertStringToInt {
+
+ @Test
+ void givenString_whenUsingAsInteger_thenConvertToInteger() {
+ def stringNum = "123"
+ def invalidString = "123a"
+ Integer expectedInteger = 123
+ Integer integerNum = stringNum as Integer
+ def intNum = invalidString?.isInteger() ? invalidString as Integer : null
+
+ assertNull(null, intNum)
+ assertEquals(integerNum, expectedInteger)
+ }
+
+ @Test
+ void givenString_whenUsingAsInt_thenConvertToInt() {
+ def stringNum = "123"
+ int expectedInt = 123
+ int intNum = stringNum as int
+
+ assertEquals(intNum, expectedInt)
+ }
+
+ @Test
+ void givenString_whenUsingToInteger_thenConvertToInteger() {
+ def stringNum = "123"
+ int expectedInt = 123
+ int intNum = stringNum.toInteger()
+
+ assertEquals(intNum, expectedInt)
+ }
+
+ @Test
+ void givenString_whenUsingParseInt_thenConvertToInteger() {
+ def stringNum = "123"
+ int expectedInt = 123
+ int intNum = Integer.parseInt(stringNum)
+
+ assertEquals(intNum, expectedInt)
+ }
+
+ @Test
+ void givenString_whenUsingValueOf_thenConvertToInteger() {
+ def stringNum = "123"
+ int expectedInt = 123
+ int intNum = Integer.valueOf(stringNum)
+
+ assertEquals(intNum, expectedInt)
+ }
+
+ @Test
+ void givenString_whenUsingIntValue_thenConvertToInteger() {
+ def stringNum = "123"
+ int expectedInt = 123
+ int intNum = new Integer(stringNum).intValue()
+
+ assertEquals(intNum, expectedInt)
+ }
+
+ @Test
+ void givenString_whenUsingNewInteger_thenConvertToInteger() {
+ def stringNum = "123"
+ int expectedInt = 123
+ int intNum = new Integer(stringNum)
+
+ assertEquals(intNum, expectedInt)
+ }
+
+ @Test
+ void givenString_whenUsingDecimalFormat_thenConvertToInteger() {
+ def stringNum = "123"
+ int expectedInt = 123
+ DecimalFormat decimalFormat = new DecimalFormat("#")
+ int intNum = decimalFormat.parse(stringNum).intValue()
+
+ assertEquals(intNum, expectedInt)
+ }
+
+ @Test(expected = NumberFormatException.class)
+ void givenInvalidString_whenUsingAs_thenThrowNumberFormatException() {
+ def invalidString = "123a"
+ invalidString as Integer
+ }
+
+ @Test(expected = NullPointerException.class)
+ void givenNullString_whenUsingToInteger_thenThrowNullPointerException() {
+ def invalidString = null
+ invalidString.toInteger()
+ }
+
+ @Test
+ void givenString_whenUsingIsInteger_thenCheckIfCorrectValue() {
+ def invalidString = "123a"
+ def validString = "123"
+ def invalidNum = invalidString?.isInteger() ? invalidString as Integer : false
+ def correctNum = validString?.isInteger() ? validString as Integer : false
+
+ assertEquals(false, invalidNum)
+ assertEquals(123, correctNum)
+ }
+}
diff --git a/core-java-modules/core-java-14/README.md b/core-java-modules/core-java-14/README.md
index 0e8278c4f6..13bb468b30 100644
--- a/core-java-modules/core-java-14/README.md
+++ b/core-java-modules/core-java-14/README.md
@@ -7,3 +7,4 @@ This module contains articles about Java 14.
- [Guide to the @Serial Annotation in Java 14](https://www.baeldung.com/java-14-serial-annotation)
- [Java Text Blocks](https://www.baeldung.com/java-text-blocks)
- [Pattern Matching for instanceof in Java 14](https://www.baeldung.com/java-pattern-matching-instanceof)
+- [Helpful NullPointerExceptions in Java 14](https://www.baeldung.com/java-14-nullpointerexception)
diff --git a/core-java-modules/core-java-collections-maps/README.md b/core-java-modules/core-java-collections-maps/README.md
index 828f8992e1..15cb32fbe8 100644
--- a/core-java-modules/core-java-collections-maps/README.md
+++ b/core-java-modules/core-java-collections-maps/README.md
@@ -4,7 +4,6 @@ This module contains articles about Map data structures in Java.
### Relevant Articles:
- [Guide to the Guava BiMap](https://www.baeldung.com/guava-bimap)
-- [A Guide to Java HashMap](https://www.baeldung.com/java-hashmap)
- [A Guide to LinkedHashMap in Java](https://www.baeldung.com/java-linked-hashmap)
- [A Guide to TreeMap in Java](https://www.baeldung.com/java-treemap)
- [How to Store Duplicate Keys in a Map in Java?](https://www.baeldung.com/java-map-duplicate-keys)
diff --git a/core-java-modules/core-java-concurrency-advanced-3/README.md b/core-java-modules/core-java-concurrency-advanced-3/README.md
index 2c554e948b..b11cde5158 100644
--- a/core-java-modules/core-java-concurrency-advanced-3/README.md
+++ b/core-java-modules/core-java-concurrency-advanced-3/README.md
@@ -10,4 +10,5 @@ This module contains articles about advanced topics about multithreading with co
- [Guide to RejectedExecutionHandler](https://www.baeldung.com/java-rejectedexecutionhandler)
- [Guide to Work Stealing in Java](https://www.baeldung.com/java-work-stealing)
- [Asynchronous Programming in Java](https://www.baeldung.com/java-asynchronous-programming)
+- [Java Thread Deadlock and Livelock](https://www.baeldung.com/java-deadlock-livelock)
- [[<-- previous]](/core-java-modules/core-java-concurrency-advanced-2)
diff --git a/core-java-modules/core-java-concurrency-basic-2/README.md b/core-java-modules/core-java-concurrency-basic-2/README.md
index e3f7a94e14..c7143baf36 100644
--- a/core-java-modules/core-java-concurrency-basic-2/README.md
+++ b/core-java-modules/core-java-concurrency-basic-2/README.md
@@ -8,4 +8,5 @@ This module contains articles about basic Java concurrency
- [Difference Between Wait and Sleep in Java](https://www.baeldung.com/java-wait-and-sleep)
- [Guide to the Synchronized Keyword in Java](https://www.baeldung.com/java-synchronized)
- [Life Cycle of a Thread in Java](https://www.baeldung.com/java-thread-lifecycle)
-- [[<-- Prev]](/core-java-modules/core-java-concurrency-basic)
\ No newline at end of file
+- [Guide to AtomicMarkableReference](https://www.baeldung.com/java-atomicmarkablereference)
+- [[<-- Prev]](/core-java-modules/core-java-concurrency-basic)
diff --git a/core-java-modules/core-java-concurrency-testing/.gitignore b/core-java-modules/core-java-concurrency-testing/.gitignore
deleted file mode 100644
index 3de4cc647e..0000000000
--- a/core-java-modules/core-java-concurrency-testing/.gitignore
+++ /dev/null
@@ -1,26 +0,0 @@
-*.class
-
-0.*
-
-#folders#
-/target
-/neoDb*
-/data
-/src/main/webapp/WEB-INF/classes
-*/META-INF/*
-.resourceCache
-
-# Packaged files #
-*.jar
-*.war
-*.ear
-
-# Files generated by integration tests
-*.txt
-backup-pom.xml
-/bin/
-/temp
-
-#IntelliJ specific
-.idea/
-*.iml
\ No newline at end of file
diff --git a/core-java-modules/core-java-concurrency-testing/pom.xml b/core-java-modules/core-java-concurrency-testing/pom.xml
index bb3e6f5152..51de83f67c 100644
--- a/core-java-modules/core-java-concurrency-testing/pom.xml
+++ b/core-java-modules/core-java-concurrency-testing/pom.xml
@@ -1,93 +1,93 @@
- 4.0.0
- core-java-concurrency-testing
- 0.1.0-SNAPSHOT
- core-java-concurrency-testing
- jar
+ 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
+ core-java-concurrency-testing
+ 0.1.0-SNAPSHOT
+ core-java-concurrency-testing
+ jar
-
- com.baeldung
- parent-java
- 0.0.1-SNAPSHOT
- ../../parent-java
-
+
+ com.baeldung
+ parent-java
+ 0.0.1-SNAPSHOT
+ ../../parent-java
+
-
-
- junit
- junit
- 4.13
- test
-
-
- com.googlecode.thread-weaver
- threadweaver
- 0.2
- test
-
-
- com.google.code.tempus-fugit
- tempus-fugit
- 1.1
- test
-
-
- com.googlecode.multithreadedtc
- multithreadedtc
- 1.01
- test
-
-
- org.openjdk.jcstress
- jcstress-core
- 0.5
-
-
+
+
+ junit
+ junit
+ 4.13
+ test
+
+
+ com.googlecode.thread-weaver
+ threadweaver
+ 0.2
+ test
+
+
+ com.google.code.tempus-fugit
+ tempus-fugit
+ 1.1
+ test
+
+
+ com.googlecode.multithreadedtc
+ multithreadedtc
+ 1.01
+ test
+
+
+ org.openjdk.jcstress
+ jcstress-core
+ 0.5
+
+
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.1
-
- ${javac.target}
-
- ${javac.target}
-
-
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.1
+
+ ${javac.target}
+
+ ${javac.target}
+
+
-
- org.apache.maven.plugins
- maven-shade-plugin
- 2.2
-
-
- main
- package
-
- shade
-
-
- jcstress
-
-
- org.openjdk.jcstress.Main
-
-
- META-INF/TestList
-
-
-
-
-
-
-
-
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.2
+
+
+ main
+ package
+
+ shade
+
+
+ jcstress
+
+
+ org.openjdk.jcstress.Main
+
+
+ META-INF/TestList
+
+
+
+
+
+
+
+
diff --git a/core-java-modules/core-java-date-operations-2/README.md b/core-java-modules/core-java-date-operations-2/README.md
index 728162ca1a..19c7b98d30 100644
--- a/core-java-modules/core-java-date-operations-2/README.md
+++ b/core-java-modules/core-java-date-operations-2/README.md
@@ -7,4 +7,5 @@ This module contains articles about date operations in Java.
- [Checking If Two Java Dates Are on the Same Day](https://www.baeldung.com/java-check-two-dates-on-same-day)
- [Converting Java Date to OffsetDateTime](https://www.baeldung.com/java-convert-date-to-offsetdatetime)
- [How to Set the JVM Time Zone](https://www.baeldung.com/java-jvm-time-zone)
+- [How to determine day of week by passing specific date in Java?](https://www.baeldung.com/java-get-day-of-week)
- [[<-- Prev]](/core-java-modules/core-java-date-operations-1)
diff --git a/core-java-modules/core-java-lang-2/README.md b/core-java-modules/core-java-lang-2/README.md
index 65d40c6a26..3ade982397 100644
--- a/core-java-modules/core-java-lang-2/README.md
+++ b/core-java-modules/core-java-lang-2/README.md
@@ -10,4 +10,5 @@ This module contains articles about core features in the Java language
- [How to Return Multiple Values From a Java Method](https://www.baeldung.com/java-method-return-multiple-values)
- [Guide to the Java finally Keyword](https://www.baeldung.com/java-finally-keyword)
- [The Java Headless Mode](https://www.baeldung.com/java-headless-mode)
+- [Comparing Long Values in Java](https://www.baeldung.com/java-compare-long-values)
- [[<-- Prev]](/core-java-modules/core-java-lang)
diff --git a/core-java-modules/core-java-optional/src/main/java/com/baeldung/orelseoptional/ItemsProvider.java b/core-java-modules/core-java-optional/src/main/java/com/baeldung/orelseoptional/ItemsProvider.java
new file mode 100644
index 0000000000..480dc782e4
--- /dev/null
+++ b/core-java-modules/core-java-optional/src/main/java/com/baeldung/orelseoptional/ItemsProvider.java
@@ -0,0 +1,21 @@
+package com.baeldung.orelseoptional;
+
+import java.util.Optional;
+
+public class ItemsProvider {
+
+ Optional getEmptyItem() {
+ System.out.println("Returning an empty item");
+ return Optional.empty();
+ }
+
+ Optional getNail() {
+ System.out.println("Returning a nail");
+ return Optional.of("nail");
+ }
+
+ Optional getHammer() {
+ System.out.println("Returning a hammer");
+ return Optional.of("hammer");
+ }
+}
\ No newline at end of file
diff --git a/core-java-modules/core-java-optional/src/test/java/com/baeldung/orelseoptional/OrElseOptionalUnitTest.java b/core-java-modules/core-java-optional/src/test/java/com/baeldung/orelseoptional/OrElseOptionalUnitTest.java
index 91aebbeebd..e3bdedcd82 100644
--- a/core-java-modules/core-java-optional/src/test/java/com/baeldung/orelseoptional/OrElseOptionalUnitTest.java
+++ b/core-java-modules/core-java-optional/src/test/java/com/baeldung/orelseoptional/OrElseOptionalUnitTest.java
@@ -1,10 +1,10 @@
package com.baeldung.orelseoptional;
-import static org.junit.Assert.assertEquals;
+import org.junit.Test;
import java.util.Optional;
-import org.junit.Test;
+import static org.junit.Assert.assertEquals;
public class OrElseOptionalUnitTest {
@@ -25,6 +25,28 @@ public class OrElseOptionalUnitTest {
assertEquals(fallbackOptionalString, OptionalUtils.or(optionalString, fallbackOptionalString));
}
+ @Test
+ public void givenTwoOptionalMethods_whenFirstEmpty_thenSecondEvaluated() {
+ ItemsProvider itemsProvider = new ItemsProvider();
+
+ Optional item = itemsProvider.getEmptyItem()
+ .map(Optional::of)
+ .orElseGet(itemsProvider::getNail);
+
+ assertEquals(Optional.of("nail"), item);
+ }
+
+ @Test
+ public void givenTwoOptionalMethods_whenFirstNonEmpty_thenSecondNotEvaluated() {
+ ItemsProvider itemsProvider = new ItemsProvider();
+
+ Optional item = itemsProvider.getNail()
+ .map(Optional::of)
+ .orElseGet(itemsProvider::getHammer);
+
+ assertEquals(Optional.of("nail"), item);
+ }
+
// Uncomment code when code base is compatible with Java 9
// @Test
// public void givenOptional_whenEmptyValue_thenCustomMessage() {
diff --git a/core-java-modules/core-java-regex/README.md b/core-java-modules/core-java-regex/README.md
index 21cd7a95a3..6fdea9f2ca 100644
--- a/core-java-modules/core-java-regex/README.md
+++ b/core-java-modules/core-java-regex/README.md
@@ -9,3 +9,4 @@
- [Pre-compile Regex Patterns Into Pattern Objects](https://www.baeldung.com/java-regex-pre-compile)
- [Difference Between Java Matcher find() and matches()](https://www.baeldung.com/java-matcher-find-vs-matches)
- [How to Use Regular Expressions to Replace Tokens in Strings](https://www.baeldung.com/java-regex-token-replacement)
+- [Regular Expressions \s and \s+ in Java](https://www.baeldung.com/java-regex-s-splus)
diff --git a/core-java-modules/core-java-security-2/README.md b/core-java-modules/core-java-security-2/README.md
index 2eb21fb77e..24a821bd4d 100644
--- a/core-java-modules/core-java-security-2/README.md
+++ b/core-java-modules/core-java-security-2/README.md
@@ -8,4 +8,5 @@ This module contains articles about core Java Security
- [MD5 Hashing in Java](http://www.baeldung.com/java-md5)
- [Hashing a Password in Java](https://www.baeldung.com/java-password-hashing)
- [SHA-256 and SHA3-256 Hashing in Java](https://www.baeldung.com/sha-256-hashing-java)
+- [Checksums in Java](https://www.baeldung.com/java-checksums)
- More articles: [[<-- prev]](/core-java-modules/core-java-security)
diff --git a/core-java-modules/core-java-streams-3/README.md b/core-java-modules/core-java-streams-3/README.md
index a739245399..05c4b99900 100644
--- a/core-java-modules/core-java-streams-3/README.md
+++ b/core-java-modules/core-java-streams-3/README.md
@@ -9,4 +9,5 @@ This module contains articles about the Stream API in Java.
- [Guide to Java 8’s Collectors](https://www.baeldung.com/java-8-collectors)
- [Primitive Type Streams in Java 8](https://www.baeldung.com/java-8-primitive-streams)
- [Debugging Java 8 Streams with IntelliJ](https://www.baeldung.com/intellij-debugging-java-streams)
+- [Add BigDecimals using the Stream API](https://www.baeldung.com/java-stream-add-bigdecimals)
- More articles: [[<-- prev>]](/../core-java-streams-2)
diff --git a/core-java-modules/core-java-string-operations-2/README.md b/core-java-modules/core-java-string-operations-2/README.md
index 5e92738f5c..bc00c6a915 100644
--- a/core-java-modules/core-java-string-operations-2/README.md
+++ b/core-java-modules/core-java-string-operations-2/README.md
@@ -11,4 +11,5 @@ This module contains articles about string operations.
- [Case-Insensitive String Matching in Java](https://www.baeldung.com/java-case-insensitive-string-matching)
- [L-Trim and R-Trim in Java](https://www.baeldung.com/l-trim-and-r-trim-in-java)
- [L-Trim and R-Trim Alternatives in Java](https://www.baeldung.com/java-trim-alternatives)
+- [Java Convert PDF to Base64](https://www.baeldung.com/java-convert-pdf-to-base64)
- More articles: [[<-- prev]](../core-java-string-operations)
diff --git a/core-java-modules/core-java-strings/README.md b/core-java-modules/core-java-strings/README.md
index 4a418db29f..5daae8394a 100644
--- a/core-java-modules/core-java-strings/README.md
+++ b/core-java-modules/core-java-strings/README.md
@@ -12,3 +12,4 @@ This module contains articles about strings in Java.
- [Java String Interview Questions and Answers](https://www.baeldung.com/java-string-interview-questions)
- [Java Multi-line String](https://www.baeldung.com/java-multiline-string)
- [Guide to Java String Pool](https://www.baeldung.com/java-string-pool)
+- [Fixing “constant string too long” Build Error](https://www.baeldung.com/java-constant-string-too-long-error)
diff --git a/core-kotlin-modules/core-kotlin-collections/README.md b/core-kotlin-modules/core-kotlin-collections/README.md
index bbea5869af..f0da2b4cfd 100644
--- a/core-kotlin-modules/core-kotlin-collections/README.md
+++ b/core-kotlin-modules/core-kotlin-collections/README.md
@@ -8,3 +8,4 @@ This module contains articles about core Kotlin collections.
- [Overview of Kotlin Collections API](https://www.baeldung.com/kotlin-collections-api)
- [Converting a List to Map in Kotlin](https://www.baeldung.com/kotlin-list-to-map)
- [Filtering Kotlin Collections](https://www.baeldung.com/kotlin-filter-collection)
+- [Collection Transformations in Kotlin](https://www.baeldung.com/kotlin-collection-transformations)
diff --git a/data-structures/src/main/java/com/baeldung/tree/BinaryTree.java b/data-structures/src/main/java/com/baeldung/tree/BinaryTree.java
index bb62714006..7469e8ba64 100644
--- a/data-structures/src/main/java/com/baeldung/tree/BinaryTree.java
+++ b/data-structures/src/main/java/com/baeldung/tree/BinaryTree.java
@@ -142,7 +142,7 @@ public class BinaryTree {
nodes.add(node.left);
}
- if (node.left != null) {
+ if (node.right != null) {
nodes.add(node.right);
}
}
diff --git a/guava-collections-map/README.md b/guava-collections-map/README.md
index b3ec5e2157..4f8743dcfb 100644
--- a/guava-collections-map/README.md
+++ b/guava-collections-map/README.md
@@ -9,4 +9,5 @@ This module contains articles about map collections in Guava
- [Guide to Guava Multimap](https://www.baeldung.com/guava-multimap)
- [Guide to Guava RangeMap](https://www.baeldung.com/guava-rangemap)
- [Initialize a HashMap in Java](https://www.baeldung.com/java-initialize-hashmap)
-- [Guide to Guava ClassToInstanceMap](https://www.baeldung.com/guava-class-to-instance-map)
\ No newline at end of file
+- [Guide to Guava ClassToInstanceMap](https://www.baeldung.com/guava-class-to-instance-map)
+- [Using Guava’s MapMaker](https://www.baeldung.com/guava-mapmaker)
diff --git a/guava/README.md b/guava/README.md
index c67a3604ea..71f76c1360 100644
--- a/guava/README.md
+++ b/guava/README.md
@@ -12,4 +12,4 @@ This module contains articles a Google Guava
- [Guide to Mathematical Utilities in Guava](https://www.baeldung.com/guava-math)
- [Bloom Filter in Java using Guava](https://www.baeldung.com/guava-bloom-filter)
- [Quick Guide to the Guava RateLimiter](https://www.baeldung.com/guava-rate-limiter)
-
+- [Introduction to Guava Throwables](https://www.baeldung.com/guava-throwables)
diff --git a/java-collections-maps-3/README.md b/java-collections-maps-3/README.md
new file mode 100644
index 0000000000..ed68eb00a0
--- /dev/null
+++ b/java-collections-maps-3/README.md
@@ -0,0 +1,8 @@
+## Java Collections Cookbooks and Examples
+
+This module contains articles about Map data structures in Java.
+
+### Relevant Articles:
+
+- More articles: [[<-- prev>]](/../java-collections-maps)
+- More articles: [[<-- prev>]](/../java-collections-maps-2)
diff --git a/java-collections-maps-3/pom.xml b/java-collections-maps-3/pom.xml
new file mode 100644
index 0000000000..3888623a7f
--- /dev/null
+++ b/java-collections-maps-3/pom.xml
@@ -0,0 +1,43 @@
+
+
+
+ com.baeldung
+ parent-java
+ 0.0.1-SNAPSHOT
+ ../parent-java
+
+
+ 4.0.0
+ java-collections-maps-3
+ 0.1.0-SNAPSHOT
+ java-collections-maps-3
+ jar
+
+
+
+ org.springframework
+ spring-core
+ ${spring.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+ org.apache.commons
+ commons-collections4
+ ${commons-collections4.version}
+
+
+
+
+ 4.1
+ 3.6.1
+ 5.2.5.RELEASE
+
+
diff --git a/java-collections-maps-3/src/test/java/com/baeldung/map/caseinsensitivekeys/CaseInsensitiveMapUnitTest.java b/java-collections-maps-3/src/test/java/com/baeldung/map/caseinsensitivekeys/CaseInsensitiveMapUnitTest.java
new file mode 100644
index 0000000000..833807c692
--- /dev/null
+++ b/java-collections-maps-3/src/test/java/com/baeldung/map/caseinsensitivekeys/CaseInsensitiveMapUnitTest.java
@@ -0,0 +1,94 @@
+package com.baeldung.map.caseinsensitivekeys;
+
+import org.apache.commons.collections4.map.CaseInsensitiveMap;
+import org.junit.Test;
+import org.springframework.util.LinkedCaseInsensitiveMap;
+import java.util.Map;
+import java.util.TreeMap;
+import static org.junit.Assert.*;
+
+public class CaseInsensitiveMapUnitTest {
+ @Test
+ public void givenCaseInsensitiveTreeMap_whenTwoEntriesAdded_thenSizeIsOne(){
+ Map treeMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ treeMap.put("abc", 1);
+ treeMap.put("ABC", 2);
+
+ assertEquals(1, treeMap.size());
+ }
+
+ @Test
+ public void givenCommonsCaseInsensitiveMap_whenTwoEntriesAdded_thenSizeIsOne(){
+ Map commonsHashMap = new CaseInsensitiveMap<>();
+ commonsHashMap.put("abc", 1);
+ commonsHashMap.put("ABC", 2);
+
+ assertEquals(1, commonsHashMap.size());
+ }
+
+ @Test
+ public void givenLinkedCaseInsensitiveMap_whenTwoEntriesAdded_thenSizeIsOne(){
+ Map linkedHashMap = new LinkedCaseInsensitiveMap<>();
+ linkedHashMap.put("abc", 1);
+ linkedHashMap.put("ABC", 2);
+
+ assertEquals(1, linkedHashMap.size());
+ }
+
+ @Test
+ public void givenCaseInsensitiveTreeMap_whenSameEntryAdded_thenValueUpdated(){
+ Map treeMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ treeMap.put("abc", 1);
+ treeMap.put("ABC", 2);
+
+ assertEquals(2, treeMap.get("aBc").intValue());
+ assertEquals(2, treeMap.get("ABc").intValue());
+ }
+
+ @Test
+ public void givenCommonsCaseInsensitiveMap_whenSameEntryAdded_thenValueUpdated(){
+ Map commonsHashMap = new CaseInsensitiveMap<>();
+ commonsHashMap.put("abc", 1);
+ commonsHashMap.put("ABC", 2);
+
+ assertEquals(2, commonsHashMap.get("aBc").intValue());
+ assertEquals(2, commonsHashMap.get("ABc").intValue());
+ }
+
+ @Test
+ public void givenLinkedCaseInsensitiveMap_whenSameEntryAdded_thenValueUpdated(){
+ Map linkedHashMap = new LinkedCaseInsensitiveMap<>();
+ linkedHashMap.put("abc", 1);
+ linkedHashMap.put("ABC", 2);
+
+ assertEquals(2, linkedHashMap.get("aBc").intValue());
+ assertEquals(2, linkedHashMap.get("ABc").intValue());
+ }
+
+ @Test
+ public void givenCaseInsensitiveTreeMap_whenEntryRemoved_thenSizeIsZero(){
+ Map treeMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ treeMap.put("abc", 3);
+ treeMap.remove("aBC");
+
+ assertEquals(0, treeMap.size());
+ }
+
+ @Test
+ public void givenCommonsCaseInsensitiveMap_whenEntryRemoved_thenSizeIsZero(){
+ Map commonsHashMap = new CaseInsensitiveMap<>();
+ commonsHashMap.put("abc", 3);
+ commonsHashMap.remove("aBC");
+
+ assertEquals(0, commonsHashMap.size());
+ }
+
+ @Test
+ public void givenLinkedCaseInsensitiveMap_whenEntryRemoved_thenSizeIsZero(){
+ Map linkedHashMap = new LinkedCaseInsensitiveMap<>();
+ linkedHashMap.put("abc", 3);
+ linkedHashMap.remove("aBC");
+
+ assertEquals(0, linkedHashMap.size());
+ }
+}
diff --git a/java-numbers-3/README.md b/java-numbers-3/README.md
index 598acfb927..f818bdb675 100644
--- a/java-numbers-3/README.md
+++ b/java-numbers-3/README.md
@@ -10,4 +10,5 @@ This module contains articles about numbers in Java.
- [Generating Random Numbers in a Range in Java](https://www.baeldung.com/java-generating-random-numbers-in-range)
- [Listing Numbers Within a Range in Java](https://www.baeldung.com/java-listing-numbers-within-a-range)
- [Fibonacci Series in Java](https://www.baeldung.com/java-fibonacci)
+- [Guide to the Number Class in Java](https://www.baeldung.com/java-number-class)
- More articles: [[<-- prev]](/java-numbers-2)
diff --git a/jhipster-5/bookstore-monolith/pom.xml b/jhipster-5/bookstore-monolith/pom.xml
index 5eaf761921..233765e0f3 100644
--- a/jhipster-5/bookstore-monolith/pom.xml
+++ b/jhipster-5/bookstore-monolith/pom.xml
@@ -7,7 +7,7 @@
0.0.1-SNAPSHOT
war
Bookstore
-
+
jhipster-5
com.baeldung.jhipster
@@ -982,7 +982,7 @@
com.github.eirslett
- frontend-maven-plugin
+ frontend-maven-plugin
${frontend-maven-plugin.version}
install-node-and-npm
diff --git a/jhipster-5/bookstore-monolith/src/test/java/com/baeldung/jhipster5/service/mapper/UserMapperUnitTest.java b/jhipster-5/bookstore-monolith/src/test/java/com/baeldung/jhipster5/service/mapper/UserMapperIntegrationTest.java
similarity index 99%
rename from jhipster-5/bookstore-monolith/src/test/java/com/baeldung/jhipster5/service/mapper/UserMapperUnitTest.java
rename to jhipster-5/bookstore-monolith/src/test/java/com/baeldung/jhipster5/service/mapper/UserMapperIntegrationTest.java
index cd6a326c06..cd49135d63 100644
--- a/jhipster-5/bookstore-monolith/src/test/java/com/baeldung/jhipster5/service/mapper/UserMapperUnitTest.java
+++ b/jhipster-5/bookstore-monolith/src/test/java/com/baeldung/jhipster5/service/mapper/UserMapperIntegrationTest.java
@@ -26,7 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BookstoreApp.class)
-public class UserMapperUnitTest {
+public class UserMapperIntegrationTest {
private static final String DEFAULT_LOGIN = "johndoe";
diff --git a/libraries-3/README.md b/libraries-3/README.md
index 62bd3b9f66..ec433960ef 100644
--- a/libraries-3/README.md
+++ b/libraries-3/README.md
@@ -16,3 +16,4 @@ Remember, for advanced libraries like [Jackson](/jackson) and [JUnit](/testing-m
- [Introduction to Takes](https://www.baeldung.com/java-takes)
- [Using NullAway to Avoid NullPointerExceptions](https://www.baeldung.com/java-nullaway)
- [Introduction to Alibaba Arthas](https://www.baeldung.com/java-alibaba-arthas-intro)
+- [Quick Guide to Spring Cloud Circuit Breaker](https://www.baeldung.com/spring-cloud-circuit-breaker)
diff --git a/libraries-concurrency/README.md b/libraries-concurrency/README.md
new file mode 100644
index 0000000000..d1ffe81fa8
--- /dev/null
+++ b/libraries-concurrency/README.md
@@ -0,0 +1,3 @@
+### Relevant Articles:
+
+- [Intro to Coroutines with Quasar](https://www.baeldung.com/java-quasar-coroutines)
diff --git a/libraries-testing/README.md b/libraries-testing/README.md
index 332debfe18..7098c10d28 100644
--- a/libraries-testing/README.md
+++ b/libraries-testing/README.md
@@ -11,3 +11,4 @@ This module contains articles about test libraries.
- [Introduction to Awaitlity](https://www.baeldung.com/awaitlity-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/dbunit)
diff --git a/libraries-testing/pom.xml b/libraries-testing/pom.xml
index 3ffbb291a0..ad6c81a3d6 100644
--- a/libraries-testing/pom.xml
+++ b/libraries-testing/pom.xml
@@ -130,6 +130,27 @@
${asciidoctor.version}
+
+ org.dbunit
+ dbunit
+ ${dbunit.version}
+ test
+
+
+
+ com.h2database
+ h2
+ ${h2.version}
+ test
+
+
+
+ org.assertj
+ assertj-core
+ ${assertj-core.version}
+ test
+
+
@@ -150,6 +171,16 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+
+ ${maven-compiler-plugin.target}
+
+
+
@@ -166,6 +197,12 @@
4.1.1
3.6.2
2.0.0.0
+ 1.4.200
+ 2.7.0
+ 3.14.0
+ 1.8
+ 1.8
+ 3.8.1
diff --git a/libraries-testing/src/test/java/com/baeldung/dbunit/ConnectionSettings.java b/libraries-testing/src/test/java/com/baeldung/dbunit/ConnectionSettings.java
new file mode 100644
index 0000000000..cc29d9c58a
--- /dev/null
+++ b/libraries-testing/src/test/java/com/baeldung/dbunit/ConnectionSettings.java
@@ -0,0 +1,8 @@
+package com.baeldung.dbunit;
+
+public class ConnectionSettings {
+ public static final String JDBC_DRIVER = org.h2.Driver.class.getName();
+ public static final String JDBC_URL = "jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;init=runscript from 'classpath:dbunit/schema.sql'";
+ public static final String USER = "sa";
+ public static final String PASSWORD = "";
+}
diff --git a/libraries-testing/src/test/java/com/baeldung/dbunit/DataSourceDBUnitTest.java b/libraries-testing/src/test/java/com/baeldung/dbunit/DataSourceDBUnitTest.java
new file mode 100644
index 0000000000..93c7e9a456
--- /dev/null
+++ b/libraries-testing/src/test/java/com/baeldung/dbunit/DataSourceDBUnitTest.java
@@ -0,0 +1,168 @@
+package com.baeldung.dbunit;
+
+import org.dbunit.Assertion;
+import org.dbunit.DataSourceBasedDBTestCase;
+import org.dbunit.assertion.DiffCollectingFailureHandler;
+import org.dbunit.assertion.Difference;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.ITable;
+import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
+import org.dbunit.operation.DatabaseOperation;
+import org.h2.jdbcx.JdbcDataSource;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.platform.commons.logging.Logger;
+import org.junit.platform.commons.logging.LoggerFactory;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import javax.sql.DataSource;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import static com.baeldung.dbunit.ConnectionSettings.JDBC_URL;
+import static java.util.stream.Collectors.joining;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.dbunit.Assertion.assertEqualsIgnoreCols;
+
+@RunWith(JUnit4.class)
+public class DataSourceDBUnitTest extends DataSourceBasedDBTestCase {
+
+ private static final Logger logger = LoggerFactory.getLogger(DataSourceDBUnitTest.class);
+
+ @Override
+ protected DataSource getDataSource() {
+ JdbcDataSource dataSource = new JdbcDataSource();
+ dataSource.setURL(JDBC_URL);
+ dataSource.setUser("sa");
+ dataSource.setPassword("");
+ return dataSource;
+ }
+
+ @Override
+ protected IDataSet getDataSet() throws Exception {
+ try (InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("dbunit/data.xml")) {
+ return new FlatXmlDataSetBuilder().build(resourceAsStream);
+ }
+ }
+
+ @Override
+ protected DatabaseOperation getSetUpOperation() {
+ return DatabaseOperation.REFRESH;
+ }
+
+ @Override
+ protected DatabaseOperation getTearDownOperation() {
+ return DatabaseOperation.DELETE_ALL;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void givenDataSet_whenSelect_thenFirstTitleIsGreyTShirt() throws SQLException {
+ final Connection connection = getDataSource().getConnection();
+
+ final ResultSet rs = connection.createStatement().executeQuery("select * from ITEMS where id = 1");
+
+ assertThat(rs.next()).isTrue();
+ assertThat(rs.getString("title")).isEqualTo("Grey T-Shirt");
+ }
+
+ @Test
+ public void givenDataSetEmptySchema_whenDataSetCreated_thenTablesAreEqual() throws Exception {
+ final IDataSet expectedDataSet = getDataSet();
+ final ITable expectedTable = expectedDataSet.getTable("CLIENTS");
+ final IDataSet databaseDataSet = getConnection().createDataSet();
+ final ITable actualTable = databaseDataSet.getTable("CLIENTS");
+ Assertion.assertEquals(expectedTable, actualTable);
+ }
+
+ @Test
+ public void givenDataSet_whenInsert_thenTableHasNewClient() throws Exception {
+ try (final InputStream is = getClass().getClassLoader().getResourceAsStream("dbunit/expected-user.xml")) {
+ final IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(is);
+ final ITable expectedTable = expectedDataSet.getTable("CLIENTS");
+ final Connection conn = getDataSource().getConnection();
+
+ conn.createStatement()
+ .executeUpdate(
+ "INSERT INTO CLIENTS (first_name, last_name) VALUES ('John', 'Jansen')");
+ final ITable actualData = getConnection()
+ .createQueryTable(
+ "result_name",
+ "SELECT * FROM CLIENTS WHERE last_name='Jansen'");
+
+ assertEqualsIgnoreCols(expectedTable, actualData, new String[] { "id" });
+ }
+ }
+
+ @Test
+ public void givenDataSet_whenDelete_thenItemIsDeleted() throws Exception {
+ final Connection connection = getConnection().getConnection();
+
+ try (final InputStream is = DataSourceDBUnitTest.class.getClassLoader().getResourceAsStream("dbunit/items_exp_delete.xml")) {
+ ITable expectedTable = (new FlatXmlDataSetBuilder().build(is)).getTable("ITEMS");
+
+ connection.createStatement().executeUpdate("delete from ITEMS where id = 2");
+
+ final IDataSet databaseDataSet = getConnection().createDataSet();
+ ITable actualTable = databaseDataSet.getTable("ITEMS");
+
+ Assertion.assertEquals(expectedTable, actualTable);
+ }
+ }
+
+ @Test
+ public void givenDataSet_whenUpdate_thenItemHasNewName() throws Exception {
+ final Connection connection = getConnection().getConnection();
+
+ try (final InputStream is = DataSourceDBUnitTest.class.getClassLoader().getResourceAsStream("dbunit/items_exp_rename.xml")) {
+ ITable expectedTable = (new FlatXmlDataSetBuilder().build(is)).getTable("ITEMS");
+
+ connection.createStatement().executeUpdate("update ITEMS set title='new name' where id = 1");
+
+ final IDataSet databaseDataSet = getConnection().createDataSet();
+ ITable actualTable = databaseDataSet.getTable("ITEMS");
+
+ Assertion.assertEquals(expectedTable, actualTable);
+ }
+ }
+
+ @Test
+ public void givenDataSet_whenInsertUnexpectedData_thenFailOnAllUnexpectedValues() throws Exception {
+ try (final InputStream is = getClass().getClassLoader().getResourceAsStream("dbunit/expected-multiple-failures.xml")) {
+ final IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(is);
+ final ITable expectedTable = expectedDataSet.getTable("ITEMS");
+ final Connection conn = getDataSource().getConnection();
+ final DiffCollectingFailureHandler collectingHandler = new DiffCollectingFailureHandler();
+
+ conn.createStatement().executeUpdate(
+ "INSERT INTO ITEMS (title, price) VALUES ('Battery', '1000000')");
+ final ITable actualData = getConnection().createDataSet().getTable("ITEMS");
+
+ Assertion.assertEquals(expectedTable, actualData, collectingHandler);
+ if (!collectingHandler.getDiffList().isEmpty()) {
+ String message = (String) collectingHandler
+ .getDiffList()
+ .stream()
+ .map(d -> formatDifference((Difference) d)).collect(joining("\n"));
+ logger.error(() -> message);
+ }
+ }
+ }
+
+ private static String formatDifference(Difference diff) {
+ return "expected value in " + diff.getExpectedTable().getTableMetaData().getTableName() + "." + diff.getColumnName() + " row " + diff.getRowIndex() + ":" + diff.getExpectedValue() + ", but was: " + diff.getActualValue();
+ }
+}
diff --git a/libraries-testing/src/test/java/com/baeldung/dbunit/OldSchoolDbUnitTest.java b/libraries-testing/src/test/java/com/baeldung/dbunit/OldSchoolDbUnitTest.java
new file mode 100644
index 0000000000..6243af9676
--- /dev/null
+++ b/libraries-testing/src/test/java/com/baeldung/dbunit/OldSchoolDbUnitTest.java
@@ -0,0 +1,159 @@
+package com.baeldung.dbunit;
+
+import org.dbunit.Assertion;
+import org.dbunit.IDatabaseTester;
+import org.dbunit.JdbcDatabaseTester;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.ITable;
+import org.dbunit.dataset.filter.DefaultColumnFilter;
+import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
+import org.dbunit.operation.DatabaseOperation;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.ResultSet;
+
+import static com.baeldung.dbunit.ConnectionSettings.JDBC_DRIVER;
+import static com.baeldung.dbunit.ConnectionSettings.JDBC_URL;
+import static com.baeldung.dbunit.ConnectionSettings.PASSWORD;
+import static com.baeldung.dbunit.ConnectionSettings.USER;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.dbunit.Assertion.assertEquals;
+
+@RunWith(JUnit4.class)
+public class OldSchoolDbUnitTest {
+
+ private static IDatabaseTester tester = null;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ tester = initDatabaseTester();
+ }
+
+ private static IDatabaseTester initDatabaseTester() throws Exception {
+ final JdbcDatabaseTester tester = new JdbcDatabaseTester(JDBC_DRIVER, JDBC_URL, USER, PASSWORD);
+ tester.setDataSet(initDataSet());
+ tester.setSetUpOperation(DatabaseOperation.REFRESH);
+ tester.setTearDownOperation(DatabaseOperation.DELETE_ALL);
+ return tester;
+ }
+
+ private static IDataSet initDataSet() throws Exception {
+ try (final InputStream is = OldSchoolDbUnitTest.class.getClassLoader().getResourceAsStream("dbunit/data.xml")) {
+ return new FlatXmlDataSetBuilder().build(is);
+ }
+ }
+
+ @Before
+ public void setup() throws Exception {
+ tester.onSetup();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ tester.onTearDown();
+ }
+
+ @Test
+ public void givenDataSet_whenSelect_thenFirstTitleIsGreyTShirt() throws Exception {
+ final Connection connection = tester.getConnection().getConnection();
+
+ final ResultSet rs = connection.createStatement().executeQuery("select * from ITEMS where id = 1");
+
+ assertThat(rs.next()).isTrue();
+ assertThat(rs.getString("title")).isEqualTo("Grey T-Shirt");
+ }
+
+ @Test
+ public void givenDataSet_whenInsert_thenGetResultsAreStillEqualIfIgnoringColumnsWithDifferentProduced() throws Exception {
+ final Connection connection = tester.getConnection().getConnection();
+ final String[] excludedColumns = { "id", "produced" };
+ try (final InputStream is = getClass().getClassLoader().getResourceAsStream("dbunit/expected-ignoring-registered_at.xml")) {
+ final IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(is);
+ final ITable expectedTable = DefaultColumnFilter.excludedColumnsTable(
+ expectedDataSet.getTable("ITEMS"), excludedColumns);
+
+ connection.createStatement().executeUpdate(
+ "INSERT INTO ITEMS (title, price, produced) VALUES('Necklace', 199.99, now())");
+
+ final IDataSet databaseDataSet = tester.getConnection().createDataSet();
+ final ITable actualTable = DefaultColumnFilter.excludedColumnsTable(
+ databaseDataSet.getTable("ITEMS"), excludedColumns);
+
+ Assertion.assertEquals(expectedTable, actualTable);
+ }
+ }
+
+ @Test
+ public void givenDataSet_whenDelete_thenItemIsRemoved() throws Exception {
+ final Connection connection = tester.getConnection().getConnection();
+
+ try (final InputStream is = OldSchoolDbUnitTest.class.getClassLoader().getResourceAsStream("dbunit/items_exp_delete.xml")) {
+ ITable expectedTable = new FlatXmlDataSetBuilder().build(is).getTable("ITEMS");
+
+ connection.createStatement().executeUpdate("delete from ITEMS where id = 2");
+
+ final IDataSet databaseDataSet = tester.getConnection().createDataSet();
+ ITable actualTable = databaseDataSet.getTable("ITEMS");
+
+ assertEquals(expectedTable, actualTable);
+ }
+ }
+
+ @Test
+ public void givenDataSet_whenDelete_thenItemIsRemovedAndResultsEqualIfProducedIsIgnored() throws Exception {
+ final Connection connection = tester.getConnection().getConnection();
+
+ try (final InputStream is = OldSchoolDbUnitTest.class.getClassLoader().getResourceAsStream("dbunit/items_exp_delete_no_produced.xml")) {
+ final ITable expectedTable = new FlatXmlDataSetBuilder().build(is).getTable("ITEMS");
+
+ connection.createStatement().executeUpdate("delete from ITEMS where id = 2");
+
+ final IDataSet databaseDataSet = tester.getConnection().createDataSet();
+ ITable actualTable = databaseDataSet.getTable("ITEMS");
+ actualTable = DefaultColumnFilter.excludedColumnsTable(actualTable, new String[] { "produced" });
+
+ assertEquals(expectedTable, actualTable);
+ }
+ }
+
+ @Test
+ public void givenDataSet_whenUpdate_thenItemHasNewName() throws Exception {
+ final Connection connection = tester.getConnection().getConnection();
+
+ try (final InputStream is = OldSchoolDbUnitTest.class.getClassLoader().getResourceAsStream("dbunit/items_exp_rename.xml")) {
+ final ITable expectedTable = new FlatXmlDataSetBuilder().build(is).getTable("ITEMS");
+
+ connection.createStatement().executeUpdate("update ITEMS set title='new name' where id = 1");
+
+ final IDataSet databaseDataSet = tester.getConnection().createDataSet();
+ ITable actualTable = databaseDataSet.getTable("ITEMS");
+
+ assertEquals(expectedTable, actualTable);
+ }
+ }
+
+ @Test
+ public void givenDataSet_whenUpdateWithNoProduced_thenItemHasNewName() throws Exception {
+ final Connection connection = tester.getConnection().getConnection();
+
+ try (final InputStream is = OldSchoolDbUnitTest.class.getClassLoader().getResourceAsStream("dbunit/items_exp_rename_no_produced.xml")) {
+ ITable expectedTable = new FlatXmlDataSetBuilder().build(is).getTable("ITEMS");
+ expectedTable = DefaultColumnFilter.excludedColumnsTable(expectedTable, new String[] { "produced" });
+
+ connection.createStatement().executeUpdate("update ITEMS set title='new name' where id = 1");
+
+ final IDataSet databaseDataSet = tester.getConnection().createDataSet();
+ ITable actualTable = databaseDataSet.getTable("ITEMS");
+ actualTable = DefaultColumnFilter.excludedColumnsTable(actualTable, new String[] { "produced" });
+ assertEquals(expectedTable, actualTable);
+ }
+ }
+
+}
diff --git a/libraries-testing/src/test/java/com/baeldung/jsonassert/JsonAssertUnitTest.java b/libraries-testing/src/test/java/com/baeldung/jsonassert/JsonAssertUnitTest.java
index ce9638c4af..822468e91f 100644
--- a/libraries-testing/src/test/java/com/baeldung/jsonassert/JsonAssertUnitTest.java
+++ b/libraries-testing/src/test/java/com/baeldung/jsonassert/JsonAssertUnitTest.java
@@ -78,10 +78,10 @@ public class JsonAssertUnitTest {
@Test
public void givenArray_whenComparing_thenOrderMustMatchForStrict() throws JSONException {
- String result = "[Alex, Barbera, Charlie, Xavier]";
- JSONAssert.assertEquals("[Charlie, Alex, Xavier, Barbera]", result, JSONCompareMode.LENIENT);
- JSONAssert.assertEquals("[Alex, Barbera, Charlie, Xavier]", result, JSONCompareMode.STRICT);
- JSONAssert.assertNotEquals("[Charlie, Alex, Xavier, Barbera]", result, JSONCompareMode.STRICT);
+ String result = "[Alex, Barbera, Charlie, Wolf]";
+ JSONAssert.assertEquals("[Charlie, Alex, Wolf, Barbera]", result, JSONCompareMode.LENIENT);
+ JSONAssert.assertEquals("[Alex, Barbera, Charlie, Wolf]", result, JSONCompareMode.STRICT);
+ JSONAssert.assertNotEquals("[Charlie, Alex, Wolf, Barbera]", result, JSONCompareMode.STRICT);
}
@Test
@@ -94,7 +94,7 @@ public class JsonAssertUnitTest {
@Test
public void whenComparingSizeOfArray_thenPass() throws JSONException {
- String names = "{names:[Alex, Barbera, Charlie, Xavier]}";
+ String names = "{names:[Alex, Barbera, Charlie, Wolf]}";
JSONAssert.assertEquals("{names:[4]}", names, new ArraySizeComparator(JSONCompareMode.LENIENT));
}
diff --git a/libraries-testing/src/test/resources/dbunit/data.xml b/libraries-testing/src/test/resources/dbunit/data.xml
new file mode 100644
index 0000000000..4865dec54c
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/data.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/libraries-testing/src/test/resources/dbunit/expected-ignoring-registered_at.xml b/libraries-testing/src/test/resources/dbunit/expected-ignoring-registered_at.xml
new file mode 100644
index 0000000000..ea57b6a961
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/expected-ignoring-registered_at.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libraries-testing/src/test/resources/dbunit/expected-multiple-failures.xml b/libraries-testing/src/test/resources/dbunit/expected-multiple-failures.xml
new file mode 100644
index 0000000000..ea57b6a961
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/expected-multiple-failures.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libraries-testing/src/test/resources/dbunit/expected-user.xml b/libraries-testing/src/test/resources/dbunit/expected-user.xml
new file mode 100644
index 0000000000..631dd84210
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/expected-user.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/libraries-testing/src/test/resources/dbunit/items.xml b/libraries-testing/src/test/resources/dbunit/items.xml
new file mode 100644
index 0000000000..d13e93bbe0
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/items.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/libraries-testing/src/test/resources/dbunit/items_exp_delete.xml b/libraries-testing/src/test/resources/dbunit/items_exp_delete.xml
new file mode 100644
index 0000000000..a6fa2b33e8
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/items_exp_delete.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/libraries-testing/src/test/resources/dbunit/items_exp_delete_no_produced.xml b/libraries-testing/src/test/resources/dbunit/items_exp_delete_no_produced.xml
new file mode 100644
index 0000000000..3e7f854f5f
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/items_exp_delete_no_produced.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/libraries-testing/src/test/resources/dbunit/items_exp_rename.xml b/libraries-testing/src/test/resources/dbunit/items_exp_rename.xml
new file mode 100644
index 0000000000..32f1d57cf6
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/items_exp_rename.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/libraries-testing/src/test/resources/dbunit/items_exp_rename_no_produced.xml b/libraries-testing/src/test/resources/dbunit/items_exp_rename_no_produced.xml
new file mode 100644
index 0000000000..4f14b17113
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/items_exp_rename_no_produced.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/libraries-testing/src/test/resources/dbunit/schema.sql b/libraries-testing/src/test/resources/dbunit/schema.sql
new file mode 100644
index 0000000000..c2a8d2d630
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/schema.sql
@@ -0,0 +1,28 @@
+CREATE TABLE IF NOT EXISTS CLIENTS
+(
+ `id` int AUTO_INCREMENT NOT NULL,
+ `first_name` varchar(100) NOT NULL,
+ `last_name` varchar(100) NOT NULL,
+ PRIMARY KEY (`id`)
+);
+
+CREATE TABLE IF NOT EXISTS ITEMS
+(
+ `id` int AUTO_INCREMENT NOT NULL,
+ `title` varchar(100) NOT NULL,
+ `produced` date,
+ `price` float,
+ PRIMARY KEY (`id`)
+);
+
+CREATE TABLE IF NOT EXISTS PURCHASES
+(
+ `id` int NOT NULL AUTO_INCREMENT,
+ `id_user` int NOT NULL,
+ `id_item` int NOT NULL,
+ `total_price` float NOT NULL,
+ `quantity` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ FOREIGN KEY (`id_user`) REFERENCES CLIENTS (`id`) ON DELETE CASCADE,
+ FOREIGN KEY (`id_item`) REFERENCES ITEMS (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+);
diff --git a/libraries-testing/src/test/resources/dbunit/users.xml b/libraries-testing/src/test/resources/dbunit/users.xml
new file mode 100644
index 0000000000..f04943c4cc
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/users.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/libraries-testing/src/test/resources/dbunit/users_exp_delete.xml b/libraries-testing/src/test/resources/dbunit/users_exp_delete.xml
new file mode 100644
index 0000000000..20a2f2f1a7
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/users_exp_delete.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/libraries-testing/src/test/resources/dbunit/users_exp_rename.xml b/libraries-testing/src/test/resources/dbunit/users_exp_rename.xml
new file mode 100644
index 0000000000..1ab6cf53b8
--- /dev/null
+++ b/libraries-testing/src/test/resources/dbunit/users_exp_rename.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/persistence-modules/hibernate-jpa/README.md b/persistence-modules/hibernate-jpa/README.md
index d0a253f028..fb48f975bc 100644
--- a/persistence-modules/hibernate-jpa/README.md
+++ b/persistence-modules/hibernate-jpa/README.md
@@ -13,3 +13,4 @@ This module contains articles specific to use of Hibernate as a JPA implementati
- [Enabling Transaction Locks in Spring Data JPA](https://www.baeldung.com/java-jpa-transaction-locks)
- [TransactionRequiredException Error](https://www.baeldung.com/jpa-transaction-required-exception)
- [JPA/Hibernate Persistence Context](https://www.baeldung.com/jpa-hibernate-persistence-context)
+- [Quick Guide to EntityManager#getReference()](https://www.baeldung.com/jpa-entity-manager-get-reference)
diff --git a/persistence-modules/java-cassandra/pom.xml b/persistence-modules/java-cassandra/pom.xml
index 54879fb321..091efaeff4 100644
--- a/persistence-modules/java-cassandra/pom.xml
+++ b/persistence-modules/java-cassandra/pom.xml
@@ -18,6 +18,12 @@
cassandra-driver-core
${cassandra-driver-core.version}
true
+
+
+ com.google.guava
+ guava
+
+
diff --git a/persistence-modules/java-cassandra/src/test/resources/cassandra.yaml b/persistence-modules/java-cassandra/src/test/resources/cassandra.yaml
new file mode 100644
index 0000000000..59687444d0
--- /dev/null
+++ b/persistence-modules/java-cassandra/src/test/resources/cassandra.yaml
@@ -0,0 +1,51 @@
+cluster_name: 'Test Cluster'
+hinted_handoff_enabled: true
+max_hint_window_in_ms: 10800000
+hinted_handoff_throttle_in_kb: 1024
+max_hints_delivery_threads: 2
+hints_directory: target/embeddedCassandra/hints
+authenticator: AllowAllAuthenticator
+authorizer: AllowAllAuthorizer
+permissions_validity_in_ms: 2000
+partitioner: RandomPartitioner
+data_file_directories:
+ - target/embeddedCassandra/data
+commitlog_directory: target/embeddedCassandra/commitlog
+cdc_raw_directory: target/embeddedCassandra/cdc
+disk_failure_policy: stop
+key_cache_size_in_mb:
+key_cache_save_period: 14400
+row_cache_size_in_mb: 0
+row_cache_save_period: 0
+saved_caches_directory: target/embeddedCassandra/saved_caches
+commitlog_sync: periodic
+commitlog_sync_period_in_ms: 10000
+commitlog_segment_size_in_mb: 32
+concurrent_reads: 32
+concurrent_writes: 32
+trickle_fsync: false
+trickle_fsync_interval_in_kb: 10240
+thrift_framed_transport_size_in_mb: 15
+thrift_max_message_length_in_mb: 16
+incremental_backups: false
+snapshot_before_compaction: false
+auto_snapshot: false
+column_index_size_in_kb: 64
+compaction_throughput_mb_per_sec: 16
+read_request_timeout_in_ms: 5000
+range_request_timeout_in_ms: 10000
+write_request_timeout_in_ms: 2000
+cas_contention_timeout_in_ms: 1000
+truncate_request_timeout_in_ms: 60000
+request_timeout_in_ms: 10000
+cross_node_timeout: false
+endpoint_snitch: SimpleSnitch
+dynamic_snitch_update_interval_in_ms: 100
+dynamic_snitch_reset_interval_in_ms: 600000
+dynamic_snitch_badness_threshold: 0.1
+request_scheduler: org.apache.cassandra.scheduler.NoScheduler
+index_interval: 128
+seed_provider:
+ - class_name: org.apache.cassandra.locator.SimpleSeedProvider
+ parameters:
+ - seeds: "127.0.0.1"
diff --git a/persistence-modules/java-mongodb/README.md b/persistence-modules/java-mongodb/README.md
index a8539e644f..5c3c448b03 100644
--- a/persistence-modules/java-mongodb/README.md
+++ b/persistence-modules/java-mongodb/README.md
@@ -10,3 +10,4 @@ This module contains articles about MongoDB in Java.
- [Geospatial Support in MongoDB](https://www.baeldung.com/mongodb-geospatial-support)
- [Introduction to Morphia – Java ODM for MongoDB](https://www.baeldung.com/mongodb-morphia)
- [MongoDB Aggregations Using Java](https://www.baeldung.com/java-mongodb-aggregations)
+- [MongoDB BSON to JSON](https://www.baeldung.com/bson-to-json)
diff --git a/persistence-modules/java-mongodb/src/main/java/com/baeldung/morphia/domain/Book.java b/persistence-modules/java-mongodb/src/main/java/com/baeldung/morphia/domain/Book.java
index 172c916ad9..4ed2ab8580 100644
--- a/persistence-modules/java-mongodb/src/main/java/com/baeldung/morphia/domain/Book.java
+++ b/persistence-modules/java-mongodb/src/main/java/com/baeldung/morphia/domain/Book.java
@@ -1,5 +1,7 @@
package com.baeldung.morphia.domain;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
@@ -29,28 +31,13 @@ public class Book {
private double cost;
@Reference
private Set companionBooks;
+ @Property
+ private LocalDateTime publishDate;
public Book() {
}
- public String getTitle() {
- return title;
- }
-
- public String getAuthor() {
- return author;
- }
-
- public double getCost() {
- return cost;
- }
-
- public void addCompanionBooks(Book book) {
- if (companionBooks != null)
- this.companionBooks.add(book);
- }
-
public Book(String isbn, String title, String author, double cost, Publisher publisher) {
this.isbn = isbn;
this.title = title;
@@ -60,6 +47,71 @@ public class Book {
this.companionBooks = new HashSet<>();
}
+ // Getters and setters ...
+ public String getIsbn() {
+ return isbn;
+ }
+
+ public Book setIsbn(String isbn) {
+ this.isbn = isbn;
+ return this;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public Book setTitle(String title) {
+ this.title = title;
+ return this;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public Book setAuthor(String author) {
+ this.author = author;
+ return this;
+ }
+
+ public Publisher getPublisher() {
+ return publisher;
+ }
+
+ public Book setPublisher(Publisher publisher) {
+ this.publisher = publisher;
+ return this;
+ }
+
+ public double getCost() {
+ return cost;
+ }
+
+ public Book setCost(double cost) {
+ this.cost = cost;
+ return this;
+ }
+
+ public LocalDateTime getPublishDate() {
+ return publishDate;
+ }
+
+ public Book setPublishDate(LocalDateTime publishDate) {
+ this.publishDate = publishDate;
+ return this;
+ }
+
+ public Set getCompanionBooks() {
+ return companionBooks;
+ }
+
+ public Book addCompanionBooks(Book book) {
+ if (companionBooks != null)
+ this.companionBooks.add(book);
+ return this;
+ }
+
@Override
public String toString() {
return "Book [isbn=" + isbn + ", title=" + title + ", author=" + author + ", publisher=" + publisher + ", cost=" + cost + "]";
@@ -113,4 +165,4 @@ public class Book {
return true;
}
-}
\ No newline at end of file
+}
diff --git a/persistence-modules/java-mongodb/src/test/java/com/baeldung/bsontojson/BsonToJsonIntegrationTest.java b/persistence-modules/java-mongodb/src/test/java/com/baeldung/bsontojson/BsonToJsonIntegrationTest.java
new file mode 100644
index 0000000000..e382ea4ab2
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/test/java/com/baeldung/bsontojson/BsonToJsonIntegrationTest.java
@@ -0,0 +1,142 @@
+package com.baeldung.bsontojson;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.bson.Document;
+import org.bson.json.Converter;
+import org.bson.json.JsonMode;
+import org.bson.json.JsonWriterSettings;
+import org.bson.json.StrictJsonWriter;
+import org.bson.types.ObjectId;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.jupiter.api.AfterEach;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.baeldung.morphia.domain.Book;
+import com.baeldung.morphia.domain.Publisher;
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoDatabase;
+
+import dev.morphia.Datastore;
+import dev.morphia.Morphia;
+
+public class BsonToJsonIntegrationTest {
+
+ private static final String DB_NAME = "library";
+ private static Datastore datastore;
+
+ @BeforeClass
+ public static void setUp() {
+ Morphia morphia = new Morphia();
+ morphia.mapPackage("com.baeldung.morphia");
+ datastore = morphia.createDatastore(new MongoClient(), DB_NAME);
+ datastore.ensureIndexes();
+
+ datastore.save(new Book()
+ .setIsbn("isbn")
+ .setTitle("title")
+ .setAuthor("author")
+ .setCost(3.95)
+ .setPublisher(new Publisher(new ObjectId("fffffffffffffffffffffffa"),"publisher"))
+ .setPublishDate(LocalDateTime.parse("2020-01-01T18:13:32Z", DateTimeFormatter.ISO_DATE_TIME))
+ .addCompanionBooks(new Book().setIsbn("isbn2")));
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ datastore.delete(datastore.createQuery(Book.class));
+ }
+
+ @Test
+ public void givenBsonDocument_whenUsingStandardJsonTransformation_thenJsonDateIsObjectEpochTime() {
+
+ String json = null;
+ try (MongoClient mongoClient = new MongoClient()) {
+ MongoDatabase mongoDatabase = mongoClient.getDatabase(DB_NAME);
+ Document bson = mongoDatabase.getCollection("Books").find().first();
+ json = bson.toJson();
+ }
+
+ String expectedJson = "{\"_id\": \"isbn\", " +
+ "\"className\": \"com.baeldung.morphia.domain.Book\", " +
+ "\"title\": \"title\", " +
+ "\"author\": \"author\", " +
+ "\"publisher\": {\"_id\": {\"$oid\": \"fffffffffffffffffffffffa\"}, " +
+ "\"name\": \"publisher\"}, " +
+ "\"price\": 3.95, " +
+ "\"publishDate\": {\"$date\": 1577898812000}}";
+
+ assertNotNull(json);
+
+ assertEquals(expectedJson, json);
+ }
+
+
+ @Test
+ public void givenBsonDocument_whenUsingRelaxedJsonTransformation_thenJsonDateIsObjectIsoDate() {
+
+ String json = null;
+ try (MongoClient mongoClient = new MongoClient()) {
+ MongoDatabase mongoDatabase = mongoClient.getDatabase(DB_NAME);
+ Document bson = mongoDatabase.getCollection("Books").find().first();
+ json = bson.toJson(JsonWriterSettings
+ .builder()
+ .outputMode(JsonMode.RELAXED)
+ .build());
+ }
+
+ String expectedJson = "{\"_id\": \"isbn\", " +
+ "\"className\": \"com.baeldung.morphia.domain.Book\", " +
+ "\"title\": \"title\", " +
+ "\"author\": \"author\", " +
+ "\"publisher\": {\"_id\": {\"$oid\": \"fffffffffffffffffffffffa\"}, " +
+ "\"name\": \"publisher\"}, " +
+ "\"price\": 3.95, " +
+ "\"publishDate\": {\"$date\": \"2020-01-01T17:13:32Z\"}}";
+
+ assertNotNull(json);
+
+ assertEquals(expectedJson, json);
+ }
+
+ @Test
+ public void givenBsonDocument_whenUsingCustomJsonTransformation_thenJsonDateIsStringField() {
+
+ String json = null;
+ try (MongoClient mongoClient = new MongoClient()) {
+ MongoDatabase mongoDatabase = mongoClient.getDatabase(DB_NAME);
+ Document bson = mongoDatabase.getCollection("Books").find().first();
+ json = bson.toJson(JsonWriterSettings
+ .builder()
+ .dateTimeConverter(new JsonDateTimeConverter())
+ .build());
+ }
+
+ String expectedJson = "{\"_id\": \"isbn\", " +
+ "\"className\": \"com.baeldung.morphia.domain.Book\", " +
+ "\"title\": \"title\", " +
+ "\"author\": \"author\", " +
+ "\"publisher\": {\"_id\": {\"$oid\": \"fffffffffffffffffffffffa\"}, " +
+ "\"name\": \"publisher\"}, " +
+ "\"price\": 3.95, " +
+ "\"publishDate\": \"2020-01-01T17:13:32Z\"}";
+
+ assertEquals(expectedJson, json);
+
+ }
+
+}
diff --git a/persistence-modules/java-mongodb/src/test/java/com/baeldung/bsontojson/JsonDateTimeConverter.java b/persistence-modules/java-mongodb/src/test/java/com/baeldung/bsontojson/JsonDateTimeConverter.java
new file mode 100644
index 0000000000..46023e363f
--- /dev/null
+++ b/persistence-modules/java-mongodb/src/test/java/com/baeldung/bsontojson/JsonDateTimeConverter.java
@@ -0,0 +1,30 @@
+package com.baeldung.bsontojson;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.bson.json.Converter;
+import org.bson.json.StrictJsonWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JsonDateTimeConverter implements Converter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(JsonDateTimeConverter.class);
+ static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_INSTANT
+ .withZone(ZoneId.of("UTC"));
+
+ @Override
+ public void convert(Long value, StrictJsonWriter writer) {
+ try {
+ Instant instant = new Date(value).toInstant();
+ String s = DATE_TIME_FORMATTER.format(instant);
+ writer.writeString(s);
+ } catch (Exception e) {
+ LOGGER.error(String.format("Fail to convert offset %d to JSON date", value), e);
+ }
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/create.sql b/persistence-modules/spring-data-jpa-4/create.sql
new file mode 100644
index 0000000000..1bbe1640a7
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/create.sql
@@ -0,0 +1,2 @@
+create table PERSON (ID int8 not null, FIRST_NAME varchar(255), LAST_NAME varchar(255), primary key (ID))
+create table person (id int8 not null, first_name varchar(255), last_name varchar(255), primary key (id))
diff --git a/persistence-modules/spring-data-jpa-4/pom.xml b/persistence-modules/spring-data-jpa-4/pom.xml
index 8a476012c7..71fc21527f 100644
--- a/persistence-modules/spring-data-jpa-4/pom.xml
+++ b/persistence-modules/spring-data-jpa-4/pom.xml
@@ -33,6 +33,11 @@
mysql-connector-java
+
+ org.postgresql
+ postgresql
+
+
com.h2database
h2
diff --git a/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/Person.java b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/Person.java
new file mode 100644
index 0000000000..cfb6e67c2c
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/Person.java
@@ -0,0 +1,35 @@
+package com.baeldung.namingstrategy;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@Entity
+public class Person {
+ @Id
+ private Long id;
+
+ private String firstName;
+
+ private String lastName;
+
+ public Person() {}
+
+ public Person(Long id, String firstName, String lastName) {
+ this.id = id;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public Long id() {
+ return id;
+ }
+
+ public String firstName() {
+ return firstName;
+ }
+
+ public String lastName() {
+ return lastName;
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/PersonRepository.java b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/PersonRepository.java
new file mode 100644
index 0000000000..3c7c25bbcb
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/PersonRepository.java
@@ -0,0 +1,6 @@
+package com.baeldung.namingstrategy;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PersonRepository extends JpaRepository {
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/QuotedLowerCaseNamingStrategy.java b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/QuotedLowerCaseNamingStrategy.java
new file mode 100644
index 0000000000..16b01e50e3
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/QuotedLowerCaseNamingStrategy.java
@@ -0,0 +1,12 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.boot.model.naming.Identifier;
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
+import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
+
+public class QuotedLowerCaseNamingStrategy extends SpringPhysicalNamingStrategy {
+ @Override
+ protected Identifier getIdentifier(String name, boolean quoted, JdbcEnvironment jdbcEnvironment) {
+ return new Identifier(name.toLowerCase(), true);
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/QuotedUpperCaseNamingStrategy.java b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/QuotedUpperCaseNamingStrategy.java
new file mode 100644
index 0000000000..3cb62aa5a2
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/QuotedUpperCaseNamingStrategy.java
@@ -0,0 +1,12 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.boot.model.naming.Identifier;
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
+import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
+
+public class QuotedUpperCaseNamingStrategy extends SpringPhysicalNamingStrategy {
+ @Override
+ protected Identifier getIdentifier(String name, boolean quoted, JdbcEnvironment jdbcEnvironment) {
+ return new Identifier(name.toUpperCase(), true);
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/SpringDataJpaNamingConventionApplication.java b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/SpringDataJpaNamingConventionApplication.java
new file mode 100644
index 0000000000..f223015db8
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/SpringDataJpaNamingConventionApplication.java
@@ -0,0 +1,7 @@
+package com.baeldung.namingstrategy;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringDataJpaNamingConventionApplication {
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/UnquotedLowerCaseNamingStrategy.java b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/UnquotedLowerCaseNamingStrategy.java
new file mode 100644
index 0000000000..69e96aee27
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/UnquotedLowerCaseNamingStrategy.java
@@ -0,0 +1,12 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.boot.model.naming.Identifier;
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
+import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
+
+public class UnquotedLowerCaseNamingStrategy extends SpringPhysicalNamingStrategy {
+ @Override
+ protected Identifier getIdentifier(String name, boolean quoted, JdbcEnvironment jdbcEnvironment) {
+ return new Identifier(name.toLowerCase(), false);
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/UnquotedUpperCaseNamingStrategy.java b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/UnquotedUpperCaseNamingStrategy.java
new file mode 100644
index 0000000000..cb87af10f4
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/main/java/com/baeldung/namingstrategy/UnquotedUpperCaseNamingStrategy.java
@@ -0,0 +1,12 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.boot.model.naming.Identifier;
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
+import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
+
+public class UnquotedUpperCaseNamingStrategy extends SpringPhysicalNamingStrategy {
+ @Override
+ protected Identifier getIdentifier(String name, boolean quoted, JdbcEnvironment jdbcEnvironment) {
+ return new Identifier(name.toUpperCase(), false);
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/QuotedLowerCaseNamingStrategyH2IntegrationTest.java b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/QuotedLowerCaseNamingStrategyH2IntegrationTest.java
new file mode 100644
index 0000000000..71a4dbda3f
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/QuotedLowerCaseNamingStrategyH2IntegrationTest.java
@@ -0,0 +1,81 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.exception.SQLGrammarException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@DataJpaTest(excludeAutoConfiguration = TestDatabaseAutoConfiguration.class)
+@TestPropertySource("quoted-lower-case-naming-strategy.properties")
+class QuotedLowerCaseNamingStrategyH2IntegrationTest {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ @BeforeEach
+ void insertPeople() {
+ personRepository.saveAll(Arrays.asList(
+ new Person(1L, "John", "Doe"),
+ new Person(2L, "Jane", "Doe"),
+ new Person(3L, "Ted", "Mosby")
+ ));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"person", "PERSON", "Person"})
+ void givenPeopleAndLowerCaseNamingStrategy_whenQueryPersonUnquoted_thenException(String tableName) {
+ Query query = entityManager.createNativeQuery("select * from " + tableName);
+
+ // Unexpected result
+ assertThrows(SQLGrammarException.class, query::getResultStream);
+ }
+
+ @Test
+ void givenPeopleAndLowerCaseNamingStrategy_whenQueryPersonQuotedUpperCase_thenException() {
+ Query query = entityManager.createNativeQuery("select * from \"PERSON\"");
+
+ // Expected result
+ assertThrows(SQLGrammarException.class, query::getResultStream);
+ }
+
+ @Test
+ void givenPeopleAndLowerCaseNamingStrategy_whenQueryPersonQuotedLowerCase_thenResult() {
+ Query query = entityManager.createNativeQuery("select * from \"person\"");
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ public Person fromDatabase(Object databaseRow) {
+ Object[] typedDatabaseRow = (Object[]) databaseRow;
+
+ return new Person(
+ ((BigInteger) typedDatabaseRow[0]).longValue(),
+ (String) typedDatabaseRow[1],
+ (String) typedDatabaseRow[2]
+ );
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/QuotedLowerCaseNamingStrategyPostgresLiveTest.java b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/QuotedLowerCaseNamingStrategyPostgresLiveTest.java
new file mode 100644
index 0000000000..6b1c984600
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/QuotedLowerCaseNamingStrategyPostgresLiveTest.java
@@ -0,0 +1,85 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.exception.SQLGrammarException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@DataJpaTest(excludeAutoConfiguration = TestDatabaseAutoConfiguration.class)
+@TestPropertySource("quoted-lower-case-naming-strategy-on-postgres.properties")
+class QuotedLowerCaseNamingStrategyPostgresLiveTest {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ @BeforeEach
+ void insertPeople() {
+ personRepository.saveAll(Arrays.asList(
+ new Person(1L, "John", "Doe"),
+ new Person(2L, "Jane", "Doe"),
+ new Person(3L, "Ted", "Mosby")
+ ));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"person", "PERSON", "Person"})
+ void givenPeopleAndLowerCaseNamingStrategy_whenQueryPersonUnquoted_thenResult(String tableName) {
+ Query query = entityManager.createNativeQuery("select * from " + tableName);
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndLowerCaseNamingStrategy_whenQueryPersonQuotedUpperCase_thenException() {
+ Query query = entityManager.createNativeQuery("select * from \"PERSON\"");
+
+ // Expected result
+ assertThrows(SQLGrammarException.class, query::getResultStream);
+ }
+
+ @Test
+ void givenPeopleAndLowerCaseNamingStrategy_whenQueryPersonQuotedLowerCase_thenResult() {
+ Query query = entityManager.createNativeQuery("select * from \"person\"");
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ public Person fromDatabase(Object databaseRow) {
+ Object[] typedDatabaseRow = (Object[]) databaseRow;
+
+ return new Person(
+ ((BigInteger) typedDatabaseRow[0]).longValue(),
+ (String) typedDatabaseRow[1],
+ (String) typedDatabaseRow[2]
+ );
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/QuotedUpperCaseNamingStrategyH2IntegrationTest.java b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/QuotedUpperCaseNamingStrategyH2IntegrationTest.java
new file mode 100644
index 0000000000..f819327a5c
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/QuotedUpperCaseNamingStrategyH2IntegrationTest.java
@@ -0,0 +1,85 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.exception.SQLGrammarException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@DataJpaTest(excludeAutoConfiguration = TestDatabaseAutoConfiguration.class)
+@TestPropertySource("quoted-upper-case-naming-strategy.properties")
+class QuotedUpperCaseNamingStrategyH2IntegrationTest {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ @BeforeEach
+ void insertPeople() {
+ personRepository.saveAll(Arrays.asList(
+ new Person(1L, "John", "Doe"),
+ new Person(2L, "Jane", "Doe"),
+ new Person(3L, "Ted", "Mosby")
+ ));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"person", "PERSON", "Person"})
+ void givenPeopleAndUpperCaseNamingStrategy_whenQueryPersonUnquoted_thenException(String tableName) {
+ Query query = entityManager.createNativeQuery("select * from " + tableName);
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndUpperCaseNamingStrategy_whenQueryPersonQuotedUpperCase_thenResult() {
+ Query query = entityManager.createNativeQuery("select * from \"PERSON\"");
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndUpperCaseNamingStrategy_whenQueryPersonQuotedLowerCase_thenException() {
+ Query query = entityManager.createNativeQuery("select * from \"person\"");
+
+ // Expected result
+ assertThrows(SQLGrammarException.class, query::getResultStream);
+ }
+
+ public Person fromDatabase(Object databaseRow) {
+ Object[] typedDatabaseRow = (Object[]) databaseRow;
+
+ return new Person(
+ ((BigInteger) typedDatabaseRow[0]).longValue(),
+ (String) typedDatabaseRow[1],
+ (String) typedDatabaseRow[2]
+ );
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/QuotedUpperCaseNamingStrategyPostgresLiveTest.java b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/QuotedUpperCaseNamingStrategyPostgresLiveTest.java
new file mode 100644
index 0000000000..bd23b81b4b
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/QuotedUpperCaseNamingStrategyPostgresLiveTest.java
@@ -0,0 +1,81 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.exception.SQLGrammarException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@DataJpaTest(excludeAutoConfiguration = TestDatabaseAutoConfiguration.class)
+@TestPropertySource("quoted-upper-case-naming-strategy-on-postgres.properties")
+class QuotedUpperCaseNamingStrategyPostgresLiveTest {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ @BeforeEach
+ void insertPeople() {
+ personRepository.saveAll(Arrays.asList(
+ new Person(1L, "John", "Doe"),
+ new Person(2L, "Jane", "Doe"),
+ new Person(3L, "Ted", "Mosby")
+ ));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"person", "PERSON", "Person"})
+ void givenPeopleAndUpperCaseNamingStrategy_whenQueryPersonUnquoted_thenResult(String tableName) {
+ Query query = entityManager.createNativeQuery("select * from " + tableName);
+
+ // Unexpected result
+ assertThrows(SQLGrammarException.class, query::getResultStream);
+ }
+
+ @Test
+ void givenPeopleAndUpperCaseNamingStrategy_whenQueryPersonQuotedUpperCase_thenException() {
+ Query query = entityManager.createNativeQuery("select * from \"PERSON\"");
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndUpperCaseNamingStrategy_whenQueryPersonQuotedLowerCase_thenResult() {
+ Query query = entityManager.createNativeQuery("select * from \"person\"");
+
+ // Expected result
+ assertThrows(SQLGrammarException.class, query::getResultStream);
+ }
+
+ public Person fromDatabase(Object databaseRow) {
+ Object[] typedDatabaseRow = (Object[]) databaseRow;
+
+ return new Person(
+ ((BigInteger) typedDatabaseRow[0]).longValue(),
+ (String) typedDatabaseRow[1],
+ (String) typedDatabaseRow[2]
+ );
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/SpringPhysicalNamingStrategyH2IntegrationTest.java b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/SpringPhysicalNamingStrategyH2IntegrationTest.java
new file mode 100644
index 0000000000..1850fea173
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/SpringPhysicalNamingStrategyH2IntegrationTest.java
@@ -0,0 +1,85 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.exception.SQLGrammarException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@DataJpaTest(excludeAutoConfiguration = TestDatabaseAutoConfiguration.class)
+@TestPropertySource("spring-physical-naming-strategy.properties")
+class SpringPhysicalNamingStrategyH2IntegrationTest {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ @BeforeEach
+ void insertPeople() {
+ personRepository.saveAll(Arrays.asList(
+ new Person(1L, "John", "Doe"),
+ new Person(2L, "Jane", "Doe"),
+ new Person(3L, "Ted", "Mosby")
+ ));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"person", "PERSON", "Person"})
+ void givenPeopleAndSpringNamingStrategy_whenQueryPersonUnquoted_thenResult(String tableName) {
+ Query query = entityManager.createNativeQuery("select * from " + tableName);
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndSpringNamingStrategy_whenQueryPersonQuotedUpperCase_thenResult() {
+ Query query = entityManager.createNativeQuery("select * from \"PERSON\"");
+
+ // Unexpected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndSpringNamingStrategy_whenQueryPersonQuotedLowerCase_thenException() {
+ Query query = entityManager.createNativeQuery("select * from \"person\"");
+
+ // Unexpected result
+ assertThrows(SQLGrammarException.class, query::getResultStream);
+ }
+
+ public Person fromDatabase(Object databaseRow) {
+ Object[] typedDatabaseRow = (Object[]) databaseRow;
+
+ return new Person(
+ ((BigInteger) typedDatabaseRow[0]).longValue(),
+ (String) typedDatabaseRow[1],
+ (String) typedDatabaseRow[2]
+ );
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/SpringPhysicalNamingStrategyPostgresLiveTest.java b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/SpringPhysicalNamingStrategyPostgresLiveTest.java
new file mode 100644
index 0000000000..e26ebb148d
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/SpringPhysicalNamingStrategyPostgresLiveTest.java
@@ -0,0 +1,85 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.exception.SQLGrammarException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@DataJpaTest(excludeAutoConfiguration = TestDatabaseAutoConfiguration.class)
+@TestPropertySource("spring-physical-naming-strategy-on-postgres.properties")
+class SpringPhysicalNamingStrategyPostgresLiveTest {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ @BeforeEach
+ void insertPeople() {
+ personRepository.saveAll(Arrays.asList(
+ new Person(1L, "John", "Doe"),
+ new Person(2L, "Jane", "Doe"),
+ new Person(3L, "Ted", "Mosby")
+ ));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"person", "PERSON", "Person"})
+ void givenPeopleAndSpringNamingStrategy_whenQueryPersonUnquoted_thenResult(String tableName) {
+ Query query = entityManager.createNativeQuery("select * from " + tableName);
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndSpringNamingStrategy_whenQueryPersonQuotedUpperCase_thenException() {
+ Query query = entityManager.createNativeQuery("select * from \"PERSON\"");
+
+ // Expected result
+ assertThrows(SQLGrammarException.class, query::getResultStream);
+ }
+
+ @Test
+ void givenPeopleAndSpringNamingStrategy_whenQueryPersonQuotedLowerCase_thenResult() {
+ Query query = entityManager.createNativeQuery("select * from \"person\"");
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ public Person fromDatabase(Object databaseRow) {
+ Object[] typedDatabaseRow = (Object[]) databaseRow;
+
+ return new Person(
+ ((BigInteger) typedDatabaseRow[0]).longValue(),
+ (String) typedDatabaseRow[1],
+ (String) typedDatabaseRow[2]
+ );
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/UnquotedLowerCaseNamingStrategyH2IntegrationTest.java b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/UnquotedLowerCaseNamingStrategyH2IntegrationTest.java
new file mode 100644
index 0000000000..6311c42e93
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/UnquotedLowerCaseNamingStrategyH2IntegrationTest.java
@@ -0,0 +1,86 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.exception.SQLGrammarException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigInteger;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@DataJpaTest(excludeAutoConfiguration = TestDatabaseAutoConfiguration.class)
+@TestPropertySource("unquoted-lower-case-naming-strategy.properties")
+class UnquotedLowerCaseNamingStrategyH2IntegrationTest {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ @BeforeEach
+ void insertPeople() {
+ personRepository.saveAll(Arrays.asList(
+ new Person(1L, "John", "Doe"),
+ new Person(2L, "Jane", "Doe"),
+ new Person(3L, "Ted", "Mosby")
+ ));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"person", "PERSON", "Person"})
+ void givenPeopleAndLowerCaseNamingStrategy_whenQueryPersonUnquoted_thenResult(String tableName) {
+ Query query = entityManager.createNativeQuery("select * from " + tableName);
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndLowerCaseNamingStrategy_whenQueryPersonQuotedUpperCase_thenResult() {
+ Query query = entityManager.createNativeQuery("select * from \"PERSON\"");
+
+ // Unexpected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndLowerCaseNamingStrategy_whenQueryPersonQuotedLowerCase_thenException() {
+ Query query = entityManager.createNativeQuery("select * from \"person\"");
+
+ // Unexpected result
+ assertThrows(SQLGrammarException.class, query::getResultStream);
+ }
+
+ public Person fromDatabase(Object databaseRow) {
+ Object[] typedDatabaseRow = (Object[]) databaseRow;
+
+ return new Person(
+ ((BigInteger) typedDatabaseRow[0]).longValue(),
+ (String) typedDatabaseRow[1],
+ (String) typedDatabaseRow[2]
+ );
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/UnquotedLowerCaseNamingStrategyPostgresLiveTest.java b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/UnquotedLowerCaseNamingStrategyPostgresLiveTest.java
new file mode 100644
index 0000000000..033a213cf5
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/UnquotedLowerCaseNamingStrategyPostgresLiveTest.java
@@ -0,0 +1,85 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.exception.SQLGrammarException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@DataJpaTest(excludeAutoConfiguration = TestDatabaseAutoConfiguration.class)
+@TestPropertySource("unquoted-lower-case-naming-strategy-on-postgres.properties")
+class UnquotedLowerCaseNamingStrategyPostgresLiveTest {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ @BeforeEach
+ void insertPeople() {
+ personRepository.saveAll(Arrays.asList(
+ new Person(1L, "John", "Doe"),
+ new Person(2L, "Jane", "Doe"),
+ new Person(3L, "Ted", "Mosby")
+ ));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"person", "PERSON", "Person"})
+ void givenPeopleAndLowerCaseNamingStrategy_whenQueryPersonUnquoted_thenResult(String tableName) {
+ Query query = entityManager.createNativeQuery("select * from " + tableName);
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndLowerCaseNamingStrategy_whenQueryPersonQuotedUpperCase_thenException() {
+ Query query = entityManager.createNativeQuery("select * from \"PERSON\"");
+
+ // Expected result
+ assertThrows(SQLGrammarException.class, query::getResultStream);
+ }
+
+ @Test
+ void givenPeopleAndLowerCaseNamingStrategy_whenQueryPersonQuotedLowerCase_thenResult() {
+ Query query = entityManager.createNativeQuery("select * from \"person\"");
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ public Person fromDatabase(Object databaseRow) {
+ Object[] typedDatabaseRow = (Object[]) databaseRow;
+
+ return new Person(
+ ((BigInteger) typedDatabaseRow[0]).longValue(),
+ (String) typedDatabaseRow[1],
+ (String) typedDatabaseRow[2]
+ );
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/UnquotedUpperCaseNamingStrategyH2IntegrationTest.java b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/UnquotedUpperCaseNamingStrategyH2IntegrationTest.java
new file mode 100644
index 0000000000..7af8001854
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/UnquotedUpperCaseNamingStrategyH2IntegrationTest.java
@@ -0,0 +1,85 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.exception.SQLGrammarException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@DataJpaTest(excludeAutoConfiguration = TestDatabaseAutoConfiguration.class)
+@TestPropertySource("unquoted-upper-case-naming-strategy.properties")
+class UnquotedUpperCaseNamingStrategyH2IntegrationTest {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ @BeforeEach
+ void insertPeople() {
+ personRepository.saveAll(Arrays.asList(
+ new Person(1L, "John", "Doe"),
+ new Person(2L, "Jane", "Doe"),
+ new Person(3L, "Ted", "Mosby")
+ ));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"person", "PERSON", "Person"})
+ void givenPeopleAndUpperCaseNamingStrategy_whenQueryPersonUnquoted_thenResult(String tableName) {
+ Query query = entityManager.createNativeQuery("select * from " + tableName);
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndUpperCaseNamingStrategy_whenQueryPersonQuotedUpperCase_thenResult() {
+ Query query = entityManager.createNativeQuery("select * from \"PERSON\"");
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndUpperCaseNamingStrategy_whenQueryPersonQuotedLowerCase_thenException() {
+ Query query = entityManager.createNativeQuery("select * from \"person\"");
+
+ // Expected result
+ assertThrows(SQLGrammarException.class, query::getResultStream);
+ }
+
+ public Person fromDatabase(Object databaseRow) {
+ Object[] typedDatabaseRow = (Object[]) databaseRow;
+
+ return new Person(
+ ((BigInteger) typedDatabaseRow[0]).longValue(),
+ (String) typedDatabaseRow[1],
+ (String) typedDatabaseRow[2]
+ );
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/UnquotedUpperCaseNamingStrategyPostgresLiveTest.java b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/UnquotedUpperCaseNamingStrategyPostgresLiveTest.java
new file mode 100644
index 0000000000..0151e7ece4
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/java/com/baeldung/namingstrategy/UnquotedUpperCaseNamingStrategyPostgresLiveTest.java
@@ -0,0 +1,85 @@
+package com.baeldung.namingstrategy;
+
+import org.hibernate.exception.SQLGrammarException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@DataJpaTest(excludeAutoConfiguration = TestDatabaseAutoConfiguration.class)
+@TestPropertySource("unquoted-upper-case-naming-strategy-on-postgres.properties")
+class UnquotedUpperCaseNamingStrategyPostgresLiveTest {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ @BeforeEach
+ void insertPeople() {
+ personRepository.saveAll(Arrays.asList(
+ new Person(1L, "John", "Doe"),
+ new Person(2L, "Jane", "Doe"),
+ new Person(3L, "Ted", "Mosby")
+ ));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"person", "PERSON", "Person"})
+ void givenPeopleAndUpperCaseNamingStrategy_whenQueryPersonUnquoted_thenResult(String tableName) {
+ Query query = entityManager.createNativeQuery("select * from " + tableName);
+
+ // Expected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void givenPeopleAndUpperCaseNamingStrategy_whenQueryPersonQuotedUpperCase_thenException() {
+ Query query = entityManager.createNativeQuery("select * from \"PERSON\"");
+
+ // Unexpected result
+ assertThrows(SQLGrammarException.class, query::getResultStream);
+ }
+
+ @Test
+ void givenPeopleAndUpperCaseNamingStrategy_whenQueryPersonQuotedLowerCase_thenResult() {
+ Query query = entityManager.createNativeQuery("select * from \"person\"");
+
+ // Unexpected result
+ List result = (List) query.getResultStream()
+ .map(this::fromDatabase)
+ .collect(Collectors.toList());
+
+ assertThat(result).isNotEmpty();
+ }
+
+ public Person fromDatabase(Object databaseRow) {
+ Object[] typedDatabaseRow = (Object[]) databaseRow;
+
+ return new Person(
+ ((BigInteger) typedDatabaseRow[0]).longValue(),
+ (String) typedDatabaseRow[1],
+ (String) typedDatabaseRow[2]
+ );
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/quoted-lower-case-naming-strategy-on-postgres.properties b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/quoted-lower-case-naming-strategy-on-postgres.properties
new file mode 100644
index 0000000000..04b29de41f
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/quoted-lower-case-naming-strategy-on-postgres.properties
@@ -0,0 +1,9 @@
+spring.datasource.url=jdbc:postgresql://localhost:5432/quoted-lower-case-strategy
+spring.datasource.username=postgres
+spring.datasource.password=root
+
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.hibernate.naming.physical-strategy=com.baeldung.namingstrategy.QuotedLowerCaseNamingStrategy
+#spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=upper-case-naming-strategy-ddl.sql
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/quoted-lower-case-naming-strategy.properties b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/quoted-lower-case-naming-strategy.properties
new file mode 100644
index 0000000000..6643c12c8a
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/quoted-lower-case-naming-strategy.properties
@@ -0,0 +1,9 @@
+spring.datasource.url=jdbc:h2:mem:quoted-lower-case-strategy
+spring.datasource.username=sa
+spring.datasource.password=
+
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.hibernate.naming.physical-strategy=com.baeldung.namingstrategy.QuotedLowerCaseNamingStrategy
+#spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=upper-case-naming-strategy-ddl.sql
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/quoted-upper-case-naming-strategy-on-postgres.properties b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/quoted-upper-case-naming-strategy-on-postgres.properties
new file mode 100644
index 0000000000..36898d5b4f
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/quoted-upper-case-naming-strategy-on-postgres.properties
@@ -0,0 +1,9 @@
+spring.datasource.url=jdbc:postgresql://localhost:5432/quoted-upper-case-strategy
+spring.datasource.username=postgres
+spring.datasource.password=root
+
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.hibernate.naming.physical-strategy=com.baeldung.namingstrategy.QuotedUpperCaseNamingStrategy
+#spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=upper-case-naming-strategy-ddl.sql
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/quoted-upper-case-naming-strategy.properties b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/quoted-upper-case-naming-strategy.properties
new file mode 100644
index 0000000000..6d56d58749
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/quoted-upper-case-naming-strategy.properties
@@ -0,0 +1,9 @@
+spring.datasource.url=jdbc:h2:mem:quoted-upper-case-strategy
+spring.datasource.username=sa
+spring.datasource.password=
+
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.hibernate.naming.physical-strategy=com.baeldung.namingstrategy.QuotedUpperCaseNamingStrategy
+#spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=upper-case-naming-strategy-ddl.sql
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/spring-physical-naming-strategy-on-postgres.properties b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/spring-physical-naming-strategy-on-postgres.properties
new file mode 100644
index 0000000000..706b12b1b6
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/spring-physical-naming-strategy-on-postgres.properties
@@ -0,0 +1,9 @@
+spring.datasource.url=jdbc:postgresql://localhost:5432/spring-strategy
+spring.datasource.username=postgres
+spring.datasource.password=root
+
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
+#spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=default-naming-strategy-ddl.sql
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/spring-physical-naming-strategy.properties b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/spring-physical-naming-strategy.properties
new file mode 100644
index 0000000000..c9a0c6f24c
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/spring-physical-naming-strategy.properties
@@ -0,0 +1,9 @@
+spring.datasource.url=jdbc:h2:mem:spring-strategy
+spring.datasource.username=sa
+spring.datasource.password=
+
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
+#spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=default-naming-strategy-ddl.sql
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/unquoted-lower-case-naming-strategy-on-postgres.properties b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/unquoted-lower-case-naming-strategy-on-postgres.properties
new file mode 100644
index 0000000000..b22472bd8f
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/unquoted-lower-case-naming-strategy-on-postgres.properties
@@ -0,0 +1,9 @@
+spring.datasource.url=jdbc:postgresql://localhost:5432/unquoted-lower-case-strategy
+spring.datasource.username=postgres
+spring.datasource.password=root
+
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.hibernate.naming.physical-strategy=com.baeldung.namingstrategy.UnquotedLowerCaseNamingStrategy
+#spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=upper-case-naming-strategy-ddl.sql
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/unquoted-lower-case-naming-strategy.properties b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/unquoted-lower-case-naming-strategy.properties
new file mode 100644
index 0000000000..8083515b4b
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/unquoted-lower-case-naming-strategy.properties
@@ -0,0 +1,9 @@
+spring.datasource.url=jdbc:h2:mem:unquoted-lower-case-strategy
+spring.datasource.username=sa
+spring.datasource.password=
+
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.hibernate.naming.physical-strategy=com.baeldung.namingstrategy.UnquotedLowerCaseNamingStrategy
+#spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=upper-case-naming-strategy-ddl.sql
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/unquoted-upper-case-naming-strategy-on-postgres.properties b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/unquoted-upper-case-naming-strategy-on-postgres.properties
new file mode 100644
index 0000000000..da03a0d7b5
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/unquoted-upper-case-naming-strategy-on-postgres.properties
@@ -0,0 +1,9 @@
+spring.datasource.url=jdbc:postgresql://localhost:5432/unquoted-upper-case-strategy
+spring.datasource.username=postgres
+spring.datasource.password=root
+
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.hibernate.naming.physical-strategy=com.baeldung.namingstrategy.UnquotedUpperCaseNamingStrategy
+#spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=upper-case-naming-strategy-ddl.sql
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/unquoted-upper-case-naming-strategy.properties b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/unquoted-upper-case-naming-strategy.properties
new file mode 100644
index 0000000000..d1b63e008c
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-4/src/test/resources/com/baeldung/namingstrategy/unquoted-upper-case-naming-strategy.properties
@@ -0,0 +1,9 @@
+spring.datasource.url=jdbc:h2:mem:unquoted-upper-case-strategy
+spring.datasource.username=sa
+spring.datasource.password=
+
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.hibernate.naming.physical-strategy=com.baeldung.namingstrategy.UnquotedUpperCaseNamingStrategy
+#spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
+#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=upper-case-naming-strategy-ddl.sql
\ No newline at end of file
diff --git a/persistence-modules/spring-persistence-simple-2/README.md b/persistence-modules/spring-persistence-simple-2/README.md
new file mode 100644
index 0000000000..a6408df8f2
--- /dev/null
+++ b/persistence-modules/spring-persistence-simple-2/README.md
@@ -0,0 +1,3 @@
+### Relevant Articles:
+
+- [Spring JdbcTemplate Unit Testing](https://www.baeldung.com/spring-jdbctemplate-testing)
diff --git a/pom.xml b/pom.xml
index 8d4632fb3e..99d0943582 100644
--- a/pom.xml
+++ b/pom.xml
@@ -453,6 +453,7 @@
java-collections-conversions
java-collections-conversions-2
+ java-collections-maps-3
javafx
@@ -564,6 +565,8 @@
rxjava-libraries
rxjava-observables
rxjava-operators
+
+ atomikos
@@ -803,7 +806,7 @@
jenkins/plugins
jhipster
- jws
+
libraries
@@ -963,6 +966,7 @@
java-collections-conversions
java-collections-conversions-2
+ java-collections-maps-3
javafx
@@ -1073,6 +1077,8 @@
rxjava-libraries
rxjava-observables
rxjava-operators
+
+ atomikos
@@ -1296,7 +1302,7 @@
jenkins/plugins
jhipster
- jws
+
libraries
diff --git a/reactor-core/src/test/java/com/baeldung/mono/MonoUnitTest.java b/reactor-core/src/test/java/com/baeldung/mono/MonoUnitTest.java
new file mode 100644
index 0000000000..f9e67b0a2f
--- /dev/null
+++ b/reactor-core/src/test/java/com/baeldung/mono/MonoUnitTest.java
@@ -0,0 +1,43 @@
+package com.baeldung.mono;
+
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+
+public class MonoUnitTest {
+ @Test
+ public void whenMonoProducesString_thenBlockAndConsume() {
+
+ String result1 = blockingHelloWorld().block();
+ assertEquals("Hello world!", result1);
+
+ String result2 = blockingHelloWorld()
+ .block(Duration.of(1000, ChronoUnit.MILLIS));
+ assertEquals("Hello world!", result2);
+
+ Optional result3 = Mono.empty().blockOptional();
+ assertEquals(Optional.empty(), result3);
+ }
+
+ @Test
+ public void whenMonoProducesString_thenConsumeNonBlocking() {
+
+ blockingHelloWorld()
+ .doOnNext(result -> assertEquals("Hello world!", result))
+ .subscribe();
+
+ blockingHelloWorld()
+ .subscribe(result -> assertEquals("Hello world!", result));
+
+ }
+
+ private Mono blockingHelloWorld() {
+ // blocking
+ return Mono.just("Hello world!");
+ }
+}
diff --git a/spring-5-mvc/README.md b/spring-5-mvc/README.md
index e98012c047..aff8bb227c 100644
--- a/spring-5-mvc/README.md
+++ b/spring-5-mvc/README.md
@@ -6,3 +6,4 @@ This module contains articles about Spring 5 model-view-controller (MVC) pattern
- [Spring Boot and Kotlin](https://www.baeldung.com/spring-boot-kotlin)
- [Spring MVC Streaming and SSE Request Processing](https://www.baeldung.com/spring-mvc-sse-streams)
- [Interface Driven Controllers in Spring](https://www.baeldung.com/spring-interface-driven-controllers)
+- [Returning Plain HTML From a Spring MVC Controller](https://www.baeldung.com/spring-mvc-return-html)
diff --git a/spring-5-security/src/main/java/com/baeldung/manuallogout/BasicAuthController.java b/spring-5-security/src/main/java/com/baeldung/manuallogout/BasicAuthController.java
new file mode 100644
index 0000000000..8f01940dce
--- /dev/null
+++ b/spring-5-security/src/main/java/com/baeldung/manuallogout/BasicAuthController.java
@@ -0,0 +1,32 @@
+package com.baeldung.manuallogout;
+
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+@Controller
+public class BasicAuthController {
+
+ @RequestMapping(value = {"/basiclogout"}, method = RequestMethod.POST)
+ public String logout(HttpServletRequest request, HttpServletResponse response) {
+ HttpSession session;
+ SecurityContextHolder.clearContext();
+ session = request.getSession(false);
+ if (session != null) {
+ session.invalidate();
+ }
+ for (Cookie cookie : request.getCookies()) {
+ String cookieName = cookie.getName();
+ Cookie cookieToDelete = new Cookie(cookieName, null);
+ cookieToDelete.setMaxAge(0);
+ response.addCookie(cookieToDelete);
+ }
+ return "redirect:/login?logout";
+ }
+}
diff --git a/spring-5-security/src/main/java/com/baeldung/manuallogout/ClearSiteDataController.java b/spring-5-security/src/main/java/com/baeldung/manuallogout/ClearSiteDataController.java
new file mode 100644
index 0000000000..7eef397da3
--- /dev/null
+++ b/spring-5-security/src/main/java/com/baeldung/manuallogout/ClearSiteDataController.java
@@ -0,0 +1,29 @@
+package com.baeldung.manuallogout;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler;
+import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter;
+import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter.Directive;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Controller
+public class ClearSiteDataController {
+
+ Directive[] SOURCE = {Directive.COOKIES, Directive.STORAGE, Directive.EXECUTION_CONTEXTS, Directive.CACHE};
+
+ @RequestMapping(value = {"/csdlogout"}, method = RequestMethod.POST)
+ public String logout(HttpServletRequest request, HttpServletResponse response) {
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ if (auth != null) {
+ ClearSiteDataHeaderWriter csdHeaderWriter = new ClearSiteDataHeaderWriter(SOURCE);
+ new HeaderWriterLogoutHandler(csdHeaderWriter).logout(request, response, auth);
+ }
+ return "redirect:/login?logout";
+ }
+}
diff --git a/spring-5-security/src/main/java/com/baeldung/manuallogout/ManualLogoutApplication.java b/spring-5-security/src/main/java/com/baeldung/manuallogout/ManualLogoutApplication.java
new file mode 100644
index 0000000000..50ea356f03
--- /dev/null
+++ b/spring-5-security/src/main/java/com/baeldung/manuallogout/ManualLogoutApplication.java
@@ -0,0 +1,11 @@
+package com.baeldung.manuallogout;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ManualLogoutApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(ManualLogoutApplication.class, args);
+ }
+}
diff --git a/spring-5-security/src/main/java/com/baeldung/manuallogout/SimpleSecurityConfiguration.java b/spring-5-security/src/main/java/com/baeldung/manuallogout/SimpleSecurityConfiguration.java
new file mode 100644
index 0000000000..6f14f6fca2
--- /dev/null
+++ b/spring-5-security/src/main/java/com/baeldung/manuallogout/SimpleSecurityConfiguration.java
@@ -0,0 +1,39 @@
+package com.baeldung.manuallogout;
+
+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.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@Configuration
+@EnableWebSecurity
+public class SimpleSecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.formLogin()
+ .loginProcessingUrl("/login")
+ .loginPage("/login")
+ .usernameParameter("username")
+ .passwordParameter("password")
+ .defaultSuccessUrl("/")
+ .failureUrl("/login?error");
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.inMemoryAuthentication()
+ .withUser("user")
+ .password("password")
+ .roles("USER")
+ .and()
+ .withUser("manager")
+ .password("password")
+ .credentialsExpired(true)
+ .accountExpired(true)
+ .accountLocked(true)
+ .authorities("WRITE_PRIVILEGES", "READ_PRIVILEGES")
+ .roles("MANAGER");
+ }
+}
diff --git a/spring-5-security/src/test/java/com/baeldung/manuallogout/ManualLogoutIntegrationTest.java b/spring-5-security/src/test/java/com/baeldung/manuallogout/ManualLogoutIntegrationTest.java
new file mode 100644
index 0000000000..a64cb82910
--- /dev/null
+++ b/spring-5-security/src/test/java/com/baeldung/manuallogout/ManualLogoutIntegrationTest.java
@@ -0,0 +1,70 @@
+package com.baeldung.manuallogout;
+
+import org.junit.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.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpSession;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpSession;
+
+import static org.junit.Assert.assertNull;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
+import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+@RunWith(SpringRunner.class)
+@WebMvcTest()
+public class ManualLogoutIntegrationTest {
+
+ private static final String CLEAR_SITE_DATA_HEADER = "Clear-Site-Data";
+ public static final int EXPIRY = 60 * 10;
+ public static final String COOKIE_NAME = "customerName";
+ public static final String COOKIE_VALUE = "myName";
+ public static final String ATTRIBUTE_NAME = "att";
+ public static final String ATTRIBUTE_VALUE = "attvalue";
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @WithMockUser(value = "spring")
+ @Test
+ public void givenLoggedUserWhenUserLogoutThenSessionCleared() throws Exception {
+
+ MockHttpSession session = new MockHttpSession();
+ session.setAttribute(ATTRIBUTE_NAME, ATTRIBUTE_VALUE);
+
+ Cookie randomCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE);
+ randomCookie.setMaxAge(EXPIRY); // 10 minutes
+
+ MockHttpServletRequest requestStateAfterLogout = this.mockMvc.perform(post("/basiclogout").secure(true).with(csrf()).session(session).cookie(randomCookie))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(unauthenticated())
+ .andExpect(cookie().maxAge(COOKIE_NAME, 0))
+ .andReturn()
+ .getRequest();
+
+ HttpSession sessionStateAfterLogout = requestStateAfterLogout.getSession();
+ assertNull(sessionStateAfterLogout.getAttribute(ATTRIBUTE_NAME));
+
+
+ }
+
+ @WithMockUser(value = "spring")
+ @Test
+ public void givenLoggedUserWhenUserLogoutThenClearDataSiteHeaderPresent() throws Exception {
+
+ this.mockMvc.perform(post("/csdlogout").secure(true).with(csrf()))
+ .andDo(print())
+ .andExpect(status().is3xxRedirection())
+ .andExpect(header().exists(CLEAR_SITE_DATA_HEADER))
+ .andReturn();
+ }
+}
\ No newline at end of file
diff --git a/spring-batch/README.md b/spring-batch/README.md
index d637de269c..3a89459629 100644
--- a/spring-batch/README.md
+++ b/spring-batch/README.md
@@ -3,6 +3,7 @@
This module contains articles about Spring Batch
### Relevant Articles:
+
- [Introduction to Spring Batch](https://www.baeldung.com/introduction-to-spring-batch)
- [Spring Batch using Partitioner](https://www.baeldung.com/spring-batch-partitioner)
- [Spring Batch – Tasklets vs Chunks](https://www.baeldung.com/spring-batch-tasklet-chunk)
@@ -10,3 +11,4 @@ This module contains articles about Spring Batch
- [Configuring Skip Logic in Spring Batch](https://www.baeldung.com/spring-batch-skip-logic)
- [Testing a Spring Batch Job](https://www.baeldung.com/spring-batch-testing-job)
- [Configuring Retry Logic in Spring Batch](https://www.baeldung.com/spring-batch-retry-logic)
+- [Conditional Flow in Spring Batch](https://www.baeldung.com/spring-batch-conditional-flow)
diff --git a/spring-boot-modules/spring-boot-libraries/pom.xml b/spring-boot-modules/spring-boot-libraries/pom.xml
index 2b1b1b7d12..090967d8a8 100644
--- a/spring-boot-modules/spring-boot-libraries/pom.xml
+++ b/spring-boot-modules/spring-boot-libraries/pom.xml
@@ -28,6 +28,10 @@
org.springframework.boot
spring-boot-starter-tomcat
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
org.springframework.boot
spring-boot-starter-test
diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/Application.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/scheduling/shedlock/SpringBootShedlockApplication.java
similarity index 71%
rename from spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/Application.java
rename to spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/scheduling/shedlock/SpringBootShedlockApplication.java
index 15422e1065..cebb234036 100644
--- a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/Application.java
+++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/scheduling/shedlock/SpringBootShedlockApplication.java
@@ -1,4 +1,4 @@
-package com.baeldung;
+package com.baeldung.scheduling.shedlock;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.boot.SpringApplication;
@@ -8,8 +8,8 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
-public class Application {
+public class SpringBootShedlockApplication {
public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
+ SpringApplication.run(SpringBootShedlockApplication.class, args);
}
}
diff --git a/spring-boot-modules/spring-boot-springdoc/pom.xml b/spring-boot-modules/spring-boot-springdoc/pom.xml
index c99a9c2b24..375cf06c2c 100644
--- a/spring-boot-modules/spring-boot-springdoc/pom.xml
+++ b/spring-boot-modules/spring-boot-springdoc/pom.xml
@@ -37,6 +37,12 @@
test
+
+ org.hibernate
+ hibernate-core
+ ${hibernate.version}
+
+
org.springdoc
@@ -61,6 +67,7 @@
1.8
+ 5.2.10.Final
1.2.32
diff --git a/spring-boot-modules/spring-boot/src/test/resources/GraphQL collection.postman_collection.json b/spring-boot-modules/spring-boot/src/test/resources/GraphQL collection.postman_collection.json
new file mode 100644
index 0000000000..f19bc1febb
--- /dev/null
+++ b/spring-boot-modules/spring-boot/src/test/resources/GraphQL collection.postman_collection.json
@@ -0,0 +1,169 @@
+{
+ "info": {
+ "_postman_id": "910d9690-f629-4491-bbbd-adb30982a386",
+ "name": "GraphQL collection",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "item": [
+ {
+ "name": "mutations",
+ "item": [
+ {
+ "name": "writePost",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "graphql",
+ "graphql": {
+ "query": "mutation writePost ($title: String!, $text: String!, $category: String) {\n writePost (title: $title, text: $text, category: $category) {\n id\n title\n text\n category\n }\n}",
+ "variables": "{\n \"title\": \"\",\n \"text\": \"\",\n \"category\": \"\"\n}"
+ },
+ "options": {
+ "graphql": {}
+ }
+ },
+ "url": {
+ "raw": "http://localhost:9090/springbootapp/graphql",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "9090",
+ "path": [
+ "springbootapp",
+ "graphql"
+ ]
+ }
+ },
+ "response": []
+ }
+ ],
+ "protocolProfileBehavior": {}
+ },
+ {
+ "name": "queries",
+ "item": [
+ {
+ "name": "get recent posts",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "graphql",
+ "graphql": {
+ "query": "{\r\n recentPosts(count: 10, offset: 0) {\r\n id\r\n title\r\n category\r\n text\r\n author {\r\n id\r\n name\r\n thumbnail\r\n }\r\n }\r\n}",
+ "variables": ""
+ }
+ },
+ "url": {
+ "raw": "http://localhost:9090/springbootapp/graphql",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "9090",
+ "path": [
+ "springbootapp",
+ "graphql"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "recentPosts - variables",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "graphql",
+ "graphql": {
+ "query": "query recentPosts ($count: Int, $offset: Int) {\n recentPosts (count: $count, offset: $offset) {\n id\n title\n text\n category\n }\n}",
+ "variables": "{\n \"count\": 1,\n \"offset\": 0\n}"
+ },
+ "options": {
+ "graphql": {}
+ }
+ },
+ "url": {
+ "raw": "http://localhost:9090/springbootapp/graphql",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "9090",
+ "path": [
+ "springbootapp",
+ "graphql"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "get recent posts - raw",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/graphql",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "query {\r\n recentPosts(count: 10, offset: 0) {\r\n id\r\n title\r\n category\r\n author {\r\n id\r\n name\r\n thumbnail\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "http://localhost:9090/springbootapp/graphql",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "9090",
+ "path": [
+ "springbootapp",
+ "graphql"
+ ]
+ }
+ },
+ "response": []
+ }
+ ],
+ "protocolProfileBehavior": {}
+ }
+ ],
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "id": "b54f267b-c450-4f2d-8105-2f23bab4c922",
+ "type": "text/javascript",
+ "exec": [
+ ""
+ ]
+ }
+ },
+ {
+ "listen": "test",
+ "script": {
+ "id": "00b575be-03d4-4b29-b137-733ead139638",
+ "type": "text/javascript",
+ "exec": [
+ ""
+ ]
+ }
+ }
+ ],
+ "variable": [
+ {
+ "id": "20a274e5-6d51-40d6-81cb-af9eb115b21b",
+ "key": "url",
+ "value": "",
+ "type": "string"
+ }
+ ],
+ "protocolProfileBehavior": {}
+}
\ No newline at end of file
diff --git a/spring-caching/pom.xml b/spring-caching/pom.xml
index c3ededbd14..d33f24de1f 100644
--- a/spring-caching/pom.xml
+++ b/spring-caching/pom.xml
@@ -19,6 +19,10 @@
org.springframework
spring-context
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
org.springframework
spring-web
diff --git a/spring-caching/src/main/java/com/baeldung/caching/boot/CacheApplication.java b/spring-caching/src/main/java/com/baeldung/caching/boot/CacheApplication.java
new file mode 100644
index 0000000000..714dc443e0
--- /dev/null
+++ b/spring-caching/src/main/java/com/baeldung/caching/boot/CacheApplication.java
@@ -0,0 +1,14 @@
+package com.baeldung.caching.boot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+
+@SpringBootApplication
+@EnableCaching
+public class CacheApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CacheApplication.class, args);
+ }
+}
diff --git a/spring-caching/src/main/java/com/baeldung/caching/boot/SimpleCacheCustomizer.java b/spring-caching/src/main/java/com/baeldung/caching/boot/SimpleCacheCustomizer.java
new file mode 100644
index 0000000000..a76a01caae
--- /dev/null
+++ b/spring-caching/src/main/java/com/baeldung/caching/boot/SimpleCacheCustomizer.java
@@ -0,0 +1,24 @@
+package com.baeldung.caching.boot;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.stereotype.Component;
+
+import static java.util.Arrays.asList;
+
+@Component
+public class SimpleCacheCustomizer implements CacheManagerCustomizer {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SimpleCacheCustomizer.class);
+
+ static final String USERS_CACHE = "users";
+ static final String TRANSACTIONS_CACHE = "transactions";
+
+ @Override
+ public void customize(ConcurrentMapCacheManager cacheManager) {
+ LOGGER.info("Customizing Cache Manager");
+ cacheManager.setCacheNames(asList(USERS_CACHE, TRANSACTIONS_CACHE));
+ }
+}
diff --git a/spring-caching/src/test/java/com/baeldung/caching/boot/SimpleCacheCustomizerIntegrationTest.java b/spring-caching/src/test/java/com/baeldung/caching/boot/SimpleCacheCustomizerIntegrationTest.java
new file mode 100644
index 0000000000..56a4bd4745
--- /dev/null
+++ b/spring-caching/src/test/java/com/baeldung/caching/boot/SimpleCacheCustomizerIntegrationTest.java
@@ -0,0 +1,24 @@
+package com.baeldung.caching.boot;
+
+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.cache.CacheManager;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class SimpleCacheCustomizerIntegrationTest {
+
+ @Autowired
+ private CacheManager cacheManager;
+
+ @Test
+ public void givenCacheManagerCustomizerWhenBootstrappedThenCacheManagerCustomized() {
+ assertThat(cacheManager.getCacheNames())
+ .containsOnly(SimpleCacheCustomizer.USERS_CACHE, SimpleCacheCustomizer.TRANSACTIONS_CACHE);
+ }
+}
\ No newline at end of file
diff --git a/spring-core-3/README.md b/spring-core-3/README.md
index b2c4f694a8..6c210b23ef 100644
--- a/spring-core-3/README.md
+++ b/spring-core-3/README.md
@@ -10,4 +10,5 @@ This module contains articles about core Spring functionality
- [Spring – Injecting Collections](https://www.baeldung.com/spring-injecting-collections)
- [Design Patterns in the Spring Framework](https://www.baeldung.com/spring-framework-design-patterns)
- [Injecting a Value in a Static Field in Spring](https://www.baeldung.com/spring-inject-static-field)
+- [Difference Between BeanFactory and ApplicationContext](https://www.baeldung.com/spring-beanfactory-vs-applicationcontext)
- More articles: [[<-- prev]](/spring-core-2)
diff --git a/spring-reactive-kotlin/README.md b/spring-reactive-kotlin/README.md
index 629f7e7f58..d6ce3b7645 100644
--- a/spring-reactive-kotlin/README.md
+++ b/spring-reactive-kotlin/README.md
@@ -4,3 +4,4 @@ This module contains articles about reactive Kotlin
### Relevant Articles:
- [Spring Webflux with Kotlin](https://www.baeldung.com/spring-webflux-kotlin)
+- [Kotlin Reactive Microservice With Spring Boot](https://www.baeldung.com/spring-boot-kotlin-reactive-microservice)
diff --git a/spring-security-modules/spring-security-mvc-login/src/test/java/com/baeldung/SpringContextTest.java b/spring-security-modules/spring-security-mvc-login/src/test/java/com/baeldung/SpringContextTest.java
index 4cf3b736ea..dfc83a40b0 100644
--- a/spring-security-modules/spring-security-mvc-login/src/test/java/com/baeldung/SpringContextTest.java
+++ b/spring-security-modules/spring-security-mvc-login/src/test/java/com/baeldung/SpringContextTest.java
@@ -7,7 +7,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration({ "/RedirectionWebSecurityConfig.xml", "/mvc-servlet.xml" })
+@ContextConfiguration({ "/RedirectionWebSecurityConfig.xml" })
@WebAppConfiguration
public class SpringContextTest {
@Test
diff --git a/spring-security-modules/spring-security-mvc-login/src/test/java/com/baeldung/security/RedirectionSecurityIntegrationTest.java b/spring-security-modules/spring-security-mvc-login/src/test/java/com/baeldung/security/RedirectionSecurityIntegrationTest.java
index 1235e2e69f..e2b444de20 100644
--- a/spring-security-modules/spring-security-mvc-login/src/test/java/com/baeldung/security/RedirectionSecurityIntegrationTest.java
+++ b/spring-security-modules/spring-security-mvc-login/src/test/java/com/baeldung/security/RedirectionSecurityIntegrationTest.java
@@ -25,7 +25,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration({ "/RedirectionWebSecurityConfig.xml", "/mvc-servlet.xml" })
+@ContextConfiguration({ "/RedirectionWebSecurityConfig.xml" })
@WebAppConfiguration
public class RedirectionSecurityIntegrationTest {
diff --git a/spring-security-modules/spring-security-mvc-login/src/test/resources/mvc-servlet.xml b/spring-security-modules/spring-security-mvc-login/src/test/resources/mvc-servlet.xml
deleted file mode 100644
index aee837c977..0000000000
--- a/spring-security-modules/spring-security-mvc-login/src/test/resources/mvc-servlet.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-mvc/README.md b/spring-security-modules/spring-security-mvc/README.md
index 7d1a492cd3..bb4cfe1a4f 100644
--- a/spring-security-modules/spring-security-mvc/README.md
+++ b/spring-security-modules/spring-security-mvc/README.md
@@ -10,6 +10,7 @@ The "Learn Spring Security" Classes: http://github.learnspringsecurity.com
- [HttpSessionListener Example – Monitoring](https://www.baeldung.com/httpsessionlistener_with_metrics)
- [Control the Session with Spring Security](https://www.baeldung.com/spring-security-session)
+- [The Clear-Site-Data Header in Spring Security](https://www.baeldung.com/spring-security-clear-site-data-header)
### Build the Project
diff --git a/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/mvcdata/BeanConfig.java b/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/mvcdata/BeanConfig.java
new file mode 100644
index 0000000000..19f0101cf2
--- /dev/null
+++ b/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/mvcdata/BeanConfig.java
@@ -0,0 +1,14 @@
+package com.baeldung.thymeleaf.mvcdata;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.baeldung.thymeleaf.mvcdata.repository.EmailData;
+
+@Configuration
+public class BeanConfig {
+ @Bean
+ public EmailData emailData() {
+ return new EmailData();
+ }
+}
diff --git a/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/mvcdata/EmailController.java b/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/mvcdata/EmailController.java
new file mode 100644
index 0000000000..1bfe3f3428
--- /dev/null
+++ b/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/mvcdata/EmailController.java
@@ -0,0 +1,63 @@
+package com.baeldung.thymeleaf.mvcdata;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import com.baeldung.thymeleaf.mvcdata.repository.EmailData;
+
+@Controller
+public class EmailController {
+ private EmailData emailData = new EmailData();
+ private ServletContext servletContext;
+
+ public EmailController(ServletContext servletContext) {
+ this.servletContext = servletContext;
+ }
+
+ @GetMapping(value = "/email/modelattributes")
+ public String emailModel(Model model) {
+ model.addAttribute("emaildata", emailData);
+ return "mvcdata/email-model-attributes";
+ }
+
+ @ModelAttribute("emailModelAttribute")
+ EmailData emailModelAttribute() {
+ return emailData;
+ }
+
+ @GetMapping(value = "/email/requestparameters")
+ public String emailRequestParameters(
+ @RequestParam(value = "emailsubject") String emailSubject,
+ @RequestParam(value = "emailcontent") String emailContent,
+ @RequestParam(value = "emailaddress") String emailAddress1,
+ @RequestParam(value = "emailaddress") String emailAddress2,
+ @RequestParam(value = "emaillocale") String emailLocale) {
+ return "mvcdata/email-request-parameters";
+ }
+
+ @GetMapping("/email/sessionattributes")
+ public String emailSessionAttributes(HttpSession httpSession) {
+ httpSession.setAttribute("emaildata", emailData);
+ return "mvcdata/email-session-attributes";
+ }
+
+ @GetMapping("/email/servletcontext")
+ public String emailServletContext() {
+ servletContext.setAttribute("emailsubject", emailData.getEmailSubject());
+ servletContext.setAttribute("emailcontent", emailData.getEmailBody());
+ servletContext.setAttribute("emailaddress", emailData.getEmailAddress1());
+ servletContext.setAttribute("emaillocale", emailData.getEmailLocale());
+ return "mvcdata/email-servlet-context";
+ }
+
+ @GetMapping("/email/beandata")
+ public String emailBeanData() {
+ return "mvcdata/email-bean-data";
+ }
+}
diff --git a/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/mvcdata/repository/EmailData.java b/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/mvcdata/repository/EmailData.java
new file mode 100644
index 0000000000..9dc25bdaa7
--- /dev/null
+++ b/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/mvcdata/repository/EmailData.java
@@ -0,0 +1,48 @@
+package com.baeldung.thymeleaf.mvcdata.repository;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class EmailData implements Serializable {
+ private String emailSubject;
+ private String emailBody;
+ private String emailLocale;
+ private String emailAddress1;
+ private String emailAddress2;
+
+ public EmailData() {
+ this.emailSubject = "You have received a new message";
+ this.emailBody = "Good morning !";
+ this.emailLocale = "en-US";
+ this.emailAddress1 = "jhon.doe@example.com";
+ this.emailAddress2 = "mark.jakob@example.com";
+ }
+
+ public String getEmailSubject() {
+ return this.emailSubject;
+ }
+
+ public String getEmailBody() {
+ return this.emailBody;
+ }
+
+ public String getEmailLocale() {
+ return this.emailLocale;
+ }
+
+ public String getEmailAddress1() {
+ return this.emailAddress1;
+ }
+
+ public String getEmailAddress2() {
+ return this.emailAddress2;
+ }
+
+ public List getEmailAddresses() {
+ List emailAddresses = new ArrayList<>();
+ emailAddresses.add(getEmailAddress1());
+ emailAddresses.add(getEmailAddress2());
+ return emailAddresses;
+ }
+}
diff --git a/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-bean-data.html b/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-bean-data.html
new file mode 100644
index 0000000000..59073b51c6
--- /dev/null
+++ b/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-bean-data.html
@@ -0,0 +1,14 @@
+
+
+
+ Subject
+ Subject
+ Content
+ Body
+ Email address
+ Email address
+ Language
+ Language
+
+
\ No newline at end of file
diff --git a/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-model-attributes.html b/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-model-attributes.html
new file mode 100644
index 0000000000..caf136383a
--- /dev/null
+++ b/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-model-attributes.html
@@ -0,0 +1,16 @@
+
+
+
+ Subject
+ Subject
+ Content
+
+ Email addresses
+
+
+
+ Language
+
+
+
\ No newline at end of file
diff --git a/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-request-parameters.html b/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-request-parameters.html
new file mode 100644
index 0000000000..8bcd3e1c62
--- /dev/null
+++ b/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-request-parameters.html
@@ -0,0 +1,20 @@
+
+
+
+ Subject
+ Subject
+ Content
+
+ Email addresses
+
+
+
+ Email address 1
+
+ Email address 2
+
+ Language
+
+
+
\ No newline at end of file
diff --git a/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-servlet-context.html b/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-servlet-context.html
new file mode 100644
index 0000000000..b07573047e
--- /dev/null
+++ b/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-servlet-context.html
@@ -0,0 +1,14 @@
+
+
+
+ Subject
+
+ Content
+
+ Email address
+
+ Language
+
+
+
\ No newline at end of file
diff --git a/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-session-attributes.html b/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-session-attributes.html
new file mode 100644
index 0000000000..9227171fc6
--- /dev/null
+++ b/spring-thymeleaf-2/src/main/resources/templates/mvcdata/email-session-attributes.html
@@ -0,0 +1,14 @@
+
+
+
+ Subject
+
+ Content
+
+ Email address
+
+ Language
+
+
+
\ No newline at end of file
diff --git a/spring-thymeleaf-2/src/test/java/com/baeldung/thymeleaf/mvcdata/EmailControllerUnitTest.java b/spring-thymeleaf-2/src/test/java/com/baeldung/thymeleaf/mvcdata/EmailControllerUnitTest.java
new file mode 100644
index 0000000000..5e1190e174
--- /dev/null
+++ b/spring-thymeleaf-2/src/test/java/com/baeldung/thymeleaf/mvcdata/EmailControllerUnitTest.java
@@ -0,0 +1,72 @@
+package com.baeldung.thymeleaf.mvcdata;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import com.baeldung.thymeleaf.mvcdata.repository.EmailData;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@AutoConfigureMockMvc(printOnlyOnFailure = false)
+public class EmailControllerUnitTest {
+
+ EmailData emailData = new EmailData();
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Test
+ public void whenCallModelAttributes_thenReturnEmailData() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/email/modelattributes"))
+ .andExpect(status().isOk())
+ .andExpect(content().string(containsString("You have received a new message")));
+ }
+
+ @Test
+ public void whenCallRequestParameters_thenReturnEmailData() throws Exception {
+ MultiValueMap params = new LinkedMultiValueMap<>();
+ params.add("emailsubject", emailData.getEmailSubject());
+ params.add("emailcontent", emailData.getEmailBody());
+ params.add("emailaddress", emailData.getEmailAddress1());
+ params.add("emailaddress", emailData.getEmailAddress2());
+ params.add("emaillocale", emailData.getEmailLocale());
+ mockMvc.perform(MockMvcRequestBuilders.get("/email/requestparameters")
+ .params(params))
+ .andExpect(status().isOk())
+ .andExpect(content().string(containsString("en-US")));
+ }
+
+ @Test
+ public void whenCallSessionAttributes_thenReturnEmailData() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/email/sessionattributes"))
+ .andExpect(status().isOk())
+ .andExpect(content().string(containsString("Good morning !")));
+ }
+
+ @Test
+ public void whenCallServletContext_thenReturnEmailData() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/email/servletcontext"))
+ .andExpect(status().isOk())
+ .andExpect(content().string(containsString("jhon.doe@example.com")));
+ }
+
+ @Test
+ public void whenCallBeanData_thenReturnEmailData() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/email/beandata"))
+ .andExpect(status().isOk())
+ .andExpect(content().string(containsString("jhon.doe@example.com")));
+ }
+
+}
diff --git a/spring-thymeleaf-3/README.md b/spring-thymeleaf-3/README.md
index e1ddd727d7..a8e234b067 100644
--- a/spring-thymeleaf-3/README.md
+++ b/spring-thymeleaf-3/README.md
@@ -2,4 +2,5 @@
This module contains articles about Spring with Thymeleaf
-## Relevant Articles:
\ No newline at end of file
+## Relevant Articles:
+- [Add CSS and JS to Thymeleaf](https://www.baeldung.com/spring-thymeleaf-css-js)
diff --git a/terraform/best-practices/README.md b/terraform/best-practices/README.md
new file mode 100644
index 0000000000..fd488b1afb
--- /dev/null
+++ b/terraform/best-practices/README.md
@@ -0,0 +1,10 @@
+# Terraform Sample Code
+
+This folder contains Terraform project samples that illustrates topics covered in the
+"Best practices when using Terraform" article. Setup instructions are available in each sample's folder.
+
+List of available samples:
+
+ * k8s-basic: "Hello world" project that just connects to a Kubernetes cluster and create a new namespace.
+ * ec2-basic: "Hello world" project that creates a single EC2 instance
+ * k8s-modules: A more elaborate sample that creates a simple set of services in a Kubernetes cluster
diff --git a/terraform/best-practices/ec2-simple/.gitignore b/terraform/best-practices/ec2-simple/.gitignore
new file mode 100644
index 0000000000..a70da3ca16
--- /dev/null
+++ b/terraform/best-practices/ec2-simple/.gitignore
@@ -0,0 +1,4 @@
+*.tfvars
+*.tfstate
+*.tfstate.backup
+.terraform
diff --git a/terraform/best-practices/ec2-simple/SETUP.md b/terraform/best-practices/ec2-simple/SETUP.md
new file mode 100644
index 0000000000..3f906b6933
--- /dev/null
+++ b/terraform/best-practices/ec2-simple/SETUP.md
@@ -0,0 +1,23 @@
+# EC2 Basic Sample
+
+This Terraform sample project creates a single EC2 instance in the configured region.
+
+IMPORTANT NOTICE: In order to run this sample you must have an active AWS Account. As you probably know, creating resources on AWS
+may result in additional charges in your bill. We recommend creating a test account to run this test as you can then use AWS's free tier
+to play around. When finished, ALWAYS REMEMBER TO DESTROY YOUR RESOURCES !!!
+
+# Setup instructions
+
+1. Make sure you have a working AWS environment. Use a simple command such as _aws ec2 describe-instances_ and check its output.
+ If you get a list of existing EC2 instances, you're good to go. Otherwise, please refer to AWS documentation in order to setup your CLI.
+2. Download the Terraform package for your environment from Hashicorp's site. Unzip it and put the _terraform_ binary somewhere
+ in the OS's PATH.
+3. Open a command prompt and _cd_ into this folder
+4. Run the following commands:
+'''
+ $ terraform init
+ $ terraform apply -auto-approve
+'''
+5. Wait until Terraform create all resources and run _aws ec2 describe-instances_. The output should list the newly creates EC2 instance
+6. Run _terraform destroy_ to remove the previously creates namespace.
+
diff --git a/terraform/best-practices/ec2-simple/main.tf b/terraform/best-practices/ec2-simple/main.tf
new file mode 100644
index 0000000000..57fb9d1757
--- /dev/null
+++ b/terraform/best-practices/ec2-simple/main.tf
@@ -0,0 +1,33 @@
+#
+# Resource definitions
+#
+
+data "aws_ami" "apache" {
+ filter {
+ name = "name"
+ values = [var.ami_name]
+ }
+
+ filter {
+ name = "virtualization-type"
+ values = ["hvm"]
+ }
+
+ owners = [var.ami_owner]
+
+ most_recent = true
+}
+
+resource "aws_instance" "web" {
+ ami = data.aws_ami.apache.id
+ instance_type = "t2.micro"
+ subnet_id = aws_subnet.frontend.id
+}
+resource "aws_subnet" "frontend" {
+ vpc_id = aws_vpc.apps.id
+ cidr_block = "10.0.1.0/24"
+}
+
+resource "aws_vpc" "apps" {
+ cidr_block = "10.0.0.0/16"
+}
diff --git a/terraform/best-practices/ec2-simple/providers.tf b/terraform/best-practices/ec2-simple/providers.tf
new file mode 100644
index 0000000000..fa5826b067
--- /dev/null
+++ b/terraform/best-practices/ec2-simple/providers.tf
@@ -0,0 +1,6 @@
+#
+# Providers definitions
+#
+provider "aws" {
+ version = "~> 2.53"
+}
\ No newline at end of file
diff --git a/terraform/best-practices/ec2-simple/variables.tf b/terraform/best-practices/ec2-simple/variables.tf
new file mode 100644
index 0000000000..2a7fddcd33
--- /dev/null
+++ b/terraform/best-practices/ec2-simple/variables.tf
@@ -0,0 +1,15 @@
+#
+# Variables
+#
+
+variable "ami_name" {
+ type = string
+ description = "AMI name to use for our EC2 instance. Defaults to Ubuntu 18.04 (Bionic)"
+ default = "ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-*"
+}
+
+variable "ami_owner" {
+ type = string
+ description = "AMI Owner ID to use for our EC2 instance. Defaults to 099720109477 (Canonical)"
+ default = "099720109477"
+}
\ No newline at end of file
diff --git a/terraform/best-practices/k8s-basic/.gitignore b/terraform/best-practices/k8s-basic/.gitignore
new file mode 100644
index 0000000000..a70da3ca16
--- /dev/null
+++ b/terraform/best-practices/k8s-basic/.gitignore
@@ -0,0 +1,4 @@
+*.tfvars
+*.tfstate
+*.tfstate.backup
+.terraform
diff --git a/terraform/best-practices/k8s-basic/SETUP.md b/terraform/best-practices/k8s-basic/SETUP.md
new file mode 100644
index 0000000000..35e690d88e
--- /dev/null
+++ b/terraform/best-practices/k8s-basic/SETUP.md
@@ -0,0 +1,14 @@
+# Setup instructions
+
+1. Make sure you have a working Kubernetes environment. Use a simple command such as _kubectl get nodes_ and check its output.
+ If you get a list of nodes that contains at least one _ready_ module, you're good to go
+2. Download the Terraform package for your environment from Hashicorp's site. Unzip it and put the _terraform_ binary somewhere
+ in the OS's PATH.
+3. Open a command prompt and _cd_ into this folder
+4. Run the following commands:
+'''
+ $ terraform init
+ $ terraform apply -auto-approve
+'''
+5. Wait until Terraform create all resources and run _kubectl get namespaces_. The output should now have a new "hello-terraform" namespace.
+6. Run _terraform destroy_ to remove the previously creates namespace.
diff --git a/terraform/best-practices/k8s-basic/main.tf b/terraform/best-practices/k8s-basic/main.tf
new file mode 100644
index 0000000000..5eb3749930
--- /dev/null
+++ b/terraform/best-practices/k8s-basic/main.tf
@@ -0,0 +1,12 @@
+#
+# Resource definitions
+#
+
+resource "kubernetes_namespace" "hello" {
+ metadata {
+ labels = {
+ terraform = "true"
+ }
+ name = var.namespace_name
+ }
+}
\ No newline at end of file
diff --git a/terraform/best-practices/k8s-basic/providers.tf b/terraform/best-practices/k8s-basic/providers.tf
new file mode 100644
index 0000000000..385f857a11
--- /dev/null
+++ b/terraform/best-practices/k8s-basic/providers.tf
@@ -0,0 +1,6 @@
+#
+# Providers definitions
+#
+provider "kubernetes" {
+ version = "~> 1.11"
+}
\ No newline at end of file
diff --git a/terraform/best-practices/k8s-basic/variables.tf b/terraform/best-practices/k8s-basic/variables.tf
new file mode 100644
index 0000000000..b9217079bf
--- /dev/null
+++ b/terraform/best-practices/k8s-basic/variables.tf
@@ -0,0 +1,9 @@
+#
+# Variables
+#
+
+variable "namespace_name" {
+ type = string
+ description = "Name to use for the created namespace"
+ default = "hello-terraform"
+}
\ No newline at end of file
diff --git a/terraform/best-practices/k8s-modules/.gitignore b/terraform/best-practices/k8s-modules/.gitignore
new file mode 100644
index 0000000000..a70da3ca16
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/.gitignore
@@ -0,0 +1,4 @@
+*.tfvars
+*.tfstate
+*.tfstate.backup
+.terraform
diff --git a/terraform/best-practices/k8s-modules/SETUP.md b/terraform/best-practices/k8s-modules/SETUP.md
new file mode 100644
index 0000000000..f00247a293
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/SETUP.md
@@ -0,0 +1,20 @@
+# Kubernetes multimodule sample
+
+This sample deploys two services behind a Kubernetes ingress.
+
+# Setup instructions
+
+1. Make sure you have a working Kubernetes environment. Use a simple command such as _kubectl get nodes_ and check its output.
+ If you get a list of nodes that contains at least one _ready_ module, you're good to go
+2. Download the Terraform package for your environment from Hashicorp's site. Unzip it and put the _terraform_ binary somewhere
+ in the OS's PATH.
+3. Open a command prompt and _cd_ into this folder
+4. Run the following commands:
+'''
+ $ terraform init
+ $ terraform apply -auto-approve
+'''
+5. Wait until Terraform create all resources and run _kubectl get services_. The output should now have a few services.
+6. Run _terraform destroy_ to remove the previously creates namespace.
+
+
diff --git a/terraform/best-practices/k8s-modules/main.tf b/terraform/best-practices/k8s-modules/main.tf
new file mode 100644
index 0000000000..86426b33db
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/main.tf
@@ -0,0 +1,27 @@
+/*
+ * Top-level definitions
+ */
+
+//================================================================== Ingress
+
+module "ingress_www_petshop_com_br" {
+ source = "./modules/ingress/www.petshop.com.br"
+ ingress_host = "www.petshop.com.br"
+}
+
+//================================================================== Deployments
+
+module "SvcCustomer" {
+ source = "./modules/SvcCustomer"
+}
+
+module "SvcFeedback" {
+ source = "./modules/SvcFeedback"
+}
+
+
+
+
+
+
+
diff --git a/terraform/best-practices/k8s-modules/modules/SvcCustomer/main.tf b/terraform/best-practices/k8s-modules/modules/SvcCustomer/main.tf
new file mode 100644
index 0000000000..1ca0c91545
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/modules/SvcCustomer/main.tf
@@ -0,0 +1,68 @@
+/*
+ * SvcCustomer deployment resources
+ */
+
+resource "kubernetes_deployment" "SvcCustomer" {
+
+ metadata {
+ name = "svccustomer"
+ labels = {
+ app = "SvcCustomer"
+ }
+ }
+
+ spec {
+ replicas = 1
+
+ selector {
+ match_labels = {
+ app = "SvcCustomer"
+ }
+ }
+
+ template {
+ metadata {
+ labels = {
+ app = "SvcCustomer"
+ }
+ }
+
+ spec {
+ image_pull_secrets {
+ name = "docker-config"
+ }
+
+
+ container {
+ image = "inanimate/echo-server"
+ name = "svccustomer-httpd"
+ env {
+ name = "PORT"
+ value = "80"
+ }
+ }
+ }
+ }
+ }
+}
+
+resource "kubernetes_service" "SvcCustomer" {
+ metadata {
+ name = "svccustomer"
+ }
+
+ spec {
+
+ selector = {
+ app = "SvcCustomer"
+ }
+
+ session_affinity = "ClientIP"
+ port {
+ port = 80
+ }
+
+ //type = "LoadBalancer"
+ }
+ }
+
\ No newline at end of file
diff --git a/terraform/best-practices/k8s-modules/modules/SvcCustomer/outputs.tf b/terraform/best-practices/k8s-modules/modules/SvcCustomer/outputs.tf
new file mode 100644
index 0000000000..8a37fdf375
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/modules/SvcCustomer/outputs.tf
@@ -0,0 +1,3 @@
+/*
+ * SvcCustomer output values
+ */
diff --git a/terraform/best-practices/k8s-modules/modules/SvcCustomer/variables.tf b/terraform/best-practices/k8s-modules/modules/SvcCustomer/variables.tf
new file mode 100644
index 0000000000..3dfcfcd264
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/modules/SvcCustomer/variables.tf
@@ -0,0 +1,5 @@
+/*
+ * SvcCustomer deployment variables
+ */
+
+
\ No newline at end of file
diff --git a/terraform/best-practices/k8s-modules/modules/SvcFeedback/main.tf b/terraform/best-practices/k8s-modules/modules/SvcFeedback/main.tf
new file mode 100644
index 0000000000..0f84c9163e
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/modules/SvcFeedback/main.tf
@@ -0,0 +1,69 @@
+/*
+ * SvcFeedback deployment resources
+ */
+
+resource "kubernetes_deployment" "SvcFeedback" {
+
+ metadata {
+ name = "svcfeedback"
+ labels = {
+ app = "SvcFeedback"
+ }
+ }
+
+ spec {
+ replicas = 1
+
+ selector {
+ match_labels = {
+ app = "SvcFeedback"
+ }
+ }
+
+ template {
+ metadata {
+ labels = {
+ app = "SvcFeedback"
+ }
+ }
+
+ spec {
+ image_pull_secrets {
+ name = "docker-config"
+ }
+
+
+ container {
+ image = "inanimate/echo-server"
+ name = "svcfeedback-httpd"
+ env {
+ name = "PORT"
+ value = "80"
+ }
+ }
+
+ }
+ }
+ }
+}
+
+resource "kubernetes_service" "SvcFeedback" {
+ metadata {
+ name = "svcfeedback"
+ }
+
+ spec {
+
+ selector = {
+ app = "SvcFeedback"
+ }
+
+ session_affinity = "ClientIP"
+ port {
+ port = 80
+ }
+
+ //type = "LoadBalancer"
+ }
+ }
+
\ No newline at end of file
diff --git a/terraform/best-practices/k8s-modules/modules/SvcFeedback/outputs.tf b/terraform/best-practices/k8s-modules/modules/SvcFeedback/outputs.tf
new file mode 100644
index 0000000000..e08c17d4b4
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/modules/SvcFeedback/outputs.tf
@@ -0,0 +1,3 @@
+/*
+ * SvcFeedback output values
+ */
diff --git a/terraform/best-practices/k8s-modules/modules/SvcFeedback/variables.tf b/terraform/best-practices/k8s-modules/modules/SvcFeedback/variables.tf
new file mode 100644
index 0000000000..d8076d41a4
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/modules/SvcFeedback/variables.tf
@@ -0,0 +1,5 @@
+/*
+ * SvcFeedback deployment variables
+ */
+
+
\ No newline at end of file
diff --git a/terraform/best-practices/k8s-modules/modules/ingress/www.petshop.com.br/main.tf b/terraform/best-practices/k8s-modules/modules/ingress/www.petshop.com.br/main.tf
new file mode 100644
index 0000000000..0dbda48981
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/modules/ingress/www.petshop.com.br/main.tf
@@ -0,0 +1,41 @@
+/*
+ * Kubernetes Ingress module
+ */
+locals {
+ iname = var.ingress_name == "" ? join("-",["ingress",sha1(uuid())]) : var.ingress_name
+}
+
+resource "kubernetes_ingress" "ingress" {
+ metadata {
+ name = local.iname
+ annotations = map(
+ "nginx.ingress.kubernetes.io/rewrite-target","/"
+ )
+ }
+ spec {
+ rule {
+ http {
+ path {
+ backend {
+ service_name = "svccustomer"
+ service_port = 80
+ }
+ path = "/customers"
+ }
+ path {
+ backend {
+ service_name = "svcfeedback"
+ service_port = 80
+ }
+ path = "/feedback"
+ }
+ }
+ }
+/*
+ tls {
+ secret_name = "tls-secret"
+ }
+*/
+ }
+}
+
diff --git a/terraform/best-practices/k8s-modules/modules/ingress/www.petshop.com.br/outputs.tf b/terraform/best-practices/k8s-modules/modules/ingress/www.petshop.com.br/outputs.tf
new file mode 100644
index 0000000000..e604417b05
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/modules/ingress/www.petshop.com.br/outputs.tf
@@ -0,0 +1,5 @@
+/*
+ * Output variables
+ */
+
+
diff --git a/terraform/best-practices/k8s-modules/modules/ingress/www.petshop.com.br/variables.tf b/terraform/best-practices/k8s-modules/modules/ingress/www.petshop.com.br/variables.tf
new file mode 100644
index 0000000000..9b06128ec5
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/modules/ingress/www.petshop.com.br/variables.tf
@@ -0,0 +1,20 @@
+/*
+ * Kubernetes Ingress module
+ */
+
+
+variable "ingress_name" {
+ type = string
+ description = "Ingress name. Defaults to a random name."
+ default = ""
+}
+
+variable "ingress_host" {
+ type = string
+ description = "Ingress hostname"
+ default = "www.petshop.com.br"
+}
+
+
+
+
diff --git a/terraform/best-practices/k8s-modules/provider.tf b/terraform/best-practices/k8s-modules/provider.tf
new file mode 100644
index 0000000000..bb90d66ecf
--- /dev/null
+++ b/terraform/best-practices/k8s-modules/provider.tf
@@ -0,0 +1,13 @@
+#
+# Provider configurations
+# This file will *NOT* be overwriten upon regeneration, so it's safe
+# to add your own customizations
+#
+
+provider "kubernetes" {
+ version = "~> 1.10"
+}
+
+provider "random" {
+ version = "~> 2.2"
+}
\ No newline at end of file
diff --git a/terraform/hello-terraform/.gitignore b/terraform/hello-terraform/.gitignore
new file mode 100644
index 0000000000..452502c50f
--- /dev/null
+++ b/terraform/hello-terraform/.gitignore
@@ -0,0 +1,6 @@
+*.tfvars
+*.tfstate
+*.tfstate.backup
+.terraform
+hello.txt
+
diff --git a/terraform/hello-terraform/main.tf b/terraform/hello-terraform/main.tf
new file mode 100644
index 0000000000..d6a726fa36
--- /dev/null
+++ b/terraform/hello-terraform/main.tf
@@ -0,0 +1,7 @@
+provider "local" {
+ version = "~> 1.4"
+}
+resource "local_file" "hello" {
+ content = "Hello, Terraform"
+ filename = "hello.txt"
+}
diff --git a/testing-modules/junit5-annotations/pom.xml b/testing-modules/junit5-annotations/pom.xml
index d0fba4d21b..9e51d0ab55 100644
--- a/testing-modules/junit5-annotations/pom.xml
+++ b/testing-modules/junit5-annotations/pom.xml
@@ -46,12 +46,19 @@
${junit.platform.version}
test
+
+ org.assertj
+ assertj-core
+ ${assertj-core.version}
+ test
+
- 5.6.0
+ 5.6.2
1.6.0
2.8.2
+ 3.11.1
diff --git a/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/templates/UserIdGenerator.java b/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/templates/UserIdGenerator.java
new file mode 100644
index 0000000000..f19adb13c8
--- /dev/null
+++ b/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/templates/UserIdGenerator.java
@@ -0,0 +1,5 @@
+package com.baeldung.junit5.templates;
+
+public interface UserIdGenerator {
+ String generate(String firstName, String lastName);
+}
diff --git a/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/templates/UserIdGeneratorImpl.java b/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/templates/UserIdGeneratorImpl.java
new file mode 100644
index 0000000000..b39b07bc4b
--- /dev/null
+++ b/testing-modules/junit5-annotations/src/main/java/com/baeldung/junit5/templates/UserIdGeneratorImpl.java
@@ -0,0 +1,15 @@
+package com.baeldung.junit5.templates;
+
+public class UserIdGeneratorImpl implements UserIdGenerator {
+ private boolean isFeatureEnabled;
+
+ public UserIdGeneratorImpl(boolean isFeatureEnabled) {
+ this.isFeatureEnabled = isFeatureEnabled;
+ }
+
+ public String generate(String firstName, String lastName) {
+ String initialAndLastName = firstName.substring(0, 1)
+ .concat(lastName);
+ return isFeatureEnabled ? "bael".concat(initialAndLastName) : initialAndLastName;
+ }
+}
diff --git a/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/DisabledOnQAEnvironmentExtension.java b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/DisabledOnQAEnvironmentExtension.java
new file mode 100644
index 0000000000..cd8b7b677d
--- /dev/null
+++ b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/DisabledOnQAEnvironmentExtension.java
@@ -0,0 +1,27 @@
+package com.baeldung.junit5.templates;
+
+import org.junit.jupiter.api.extension.ConditionEvaluationResult;
+import org.junit.jupiter.api.extension.ExecutionCondition;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public class DisabledOnQAEnvironmentExtension implements ExecutionCondition {
+ @Override
+ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
+ Properties properties = new Properties();
+ try {
+ properties.load(DisabledOnQAEnvironmentExtension.class.getClassLoader()
+ .getResourceAsStream("application.properties"));
+ if ("qa".equalsIgnoreCase(properties.getProperty("env"))) {
+ String reason = String.format("The test '%s' is disabled on QA environment", context.getDisplayName());
+ System.out.println(reason);
+ return ConditionEvaluationResult.disabled(reason);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return ConditionEvaluationResult.enabled("Test enabled");
+ }
+}
\ No newline at end of file
diff --git a/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/GenericTypedParameterResolver.java b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/GenericTypedParameterResolver.java
new file mode 100644
index 0000000000..76321be101
--- /dev/null
+++ b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/GenericTypedParameterResolver.java
@@ -0,0 +1,26 @@
+package com.baeldung.junit5.templates;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+
+public class GenericTypedParameterResolver implements ParameterResolver {
+ T data;
+
+ public GenericTypedParameterResolver(T data) {
+ this.data = data;
+ }
+
+ @Override
+ public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+ return parameterContext.getParameter()
+ .getType()
+ .isInstance(data);
+ }
+
+ @Override
+ public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+ return data;
+ }
+}
diff --git a/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/UserIdGeneratorImplUnitTest.java b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/UserIdGeneratorImplUnitTest.java
new file mode 100644
index 0000000000..a2306154a6
--- /dev/null
+++ b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/UserIdGeneratorImplUnitTest.java
@@ -0,0 +1,18 @@
+package com.baeldung.junit5.templates;
+
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UserIdGeneratorImplUnitTest {
+ @TestTemplate
+ @ExtendWith(UserIdGeneratorTestInvocationContextProvider.class)
+ public void whenUserIdRequested_thenUserIdIsReturnedInCorrectFormat(UserIdGeneratorTestCase testCase) {
+ UserIdGenerator userIdGenerator = new UserIdGeneratorImpl(testCase.isFeatureEnabled());
+
+ String actualUserId = userIdGenerator.generate(testCase.getFirstName(), testCase.getLastName());
+
+ assertThat(actualUserId).isEqualTo(testCase.getExpectedUserId());
+ }
+}
\ No newline at end of file
diff --git a/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/UserIdGeneratorTestCase.java b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/UserIdGeneratorTestCase.java
new file mode 100644
index 0000000000..dd41dd5a27
--- /dev/null
+++ b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/UserIdGeneratorTestCase.java
@@ -0,0 +1,37 @@
+package com.baeldung.junit5.templates;
+
+public class UserIdGeneratorTestCase {
+ private String displayName;
+ private boolean isFeatureEnabled;
+ private String firstName;
+ private String lastName;
+ private String expectedUserId;
+
+ public UserIdGeneratorTestCase(String displayName, boolean isFeatureEnabled, String firstName, String lastName, String expectedUserId) {
+ this.displayName = displayName;
+ this.isFeatureEnabled = isFeatureEnabled;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.expectedUserId = expectedUserId;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public boolean isFeatureEnabled() {
+ return isFeatureEnabled;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public String getExpectedUserId() {
+ return expectedUserId;
+ }
+}
diff --git a/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/UserIdGeneratorTestInvocationContextProvider.java b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/UserIdGeneratorTestInvocationContextProvider.java
new file mode 100644
index 0000000000..277ec03f05
--- /dev/null
+++ b/testing-modules/junit5-annotations/src/test/java/com/baeldung/junit5/templates/UserIdGeneratorTestInvocationContextProvider.java
@@ -0,0 +1,87 @@
+package com.baeldung.junit5.templates;
+
+import org.junit.jupiter.api.extension.*;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import static java.util.Arrays.asList;
+
+public class UserIdGeneratorTestInvocationContextProvider implements TestTemplateInvocationContextProvider {
+
+ @Override
+ public boolean supportsTestTemplate(ExtensionContext extensionContext) {
+ return true;
+ }
+
+ @Override
+ public Stream provideTestTemplateInvocationContexts(ExtensionContext extensionContext) {
+ boolean featureDisabled = false;
+ boolean featureEnabled = true;
+ return Stream.of(
+ featureDisabledContext(
+ new UserIdGeneratorTestCase(
+ "Given feature switch disabled When user name is John Smith Then generated userid is JSmith",
+ featureDisabled, "John", "Smith", "JSmith")),
+ featureEnabledContext(
+ new UserIdGeneratorTestCase(
+ "Given feature switch enabled When user name is John Smith Then generated userid is baelJSmith",
+ featureEnabled, "John", "Smith", "baelJSmith"))
+ );
+ }
+
+ private TestTemplateInvocationContext featureDisabledContext(UserIdGeneratorTestCase userIdGeneratorTestCase) {
+ return new TestTemplateInvocationContext() {
+ @Override
+ public String getDisplayName(int invocationIndex) {
+ return userIdGeneratorTestCase.getDisplayName();
+ }
+
+ @Override
+ public List getAdditionalExtensions() {
+ return asList(
+ new GenericTypedParameterResolver(userIdGeneratorTestCase),
+ new BeforeTestExecutionCallback() {
+ @Override
+ public void beforeTestExecution(ExtensionContext extensionContext) {
+ System.out.println("BeforeTestExecutionCallback:Disabled context");
+ }
+ },
+ new AfterTestExecutionCallback() {
+ @Override
+ public void afterTestExecution(ExtensionContext extensionContext) {
+ System.out.println("AfterTestExecutionCallback:Disabled context");
+ }
+ });
+ }
+ };
+ }
+
+ private TestTemplateInvocationContext featureEnabledContext(UserIdGeneratorTestCase userIdGeneratorTestCase) {
+ return new TestTemplateInvocationContext() {
+ @Override
+ public String getDisplayName(int invocationIndex) {
+ return userIdGeneratorTestCase.getDisplayName();
+ }
+
+ @Override
+ public List getAdditionalExtensions() {
+ return asList(
+ new GenericTypedParameterResolver(userIdGeneratorTestCase),
+ new DisabledOnQAEnvironmentExtension(),
+ new BeforeEachCallback() {
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) {
+ System.out.println("BeforeEachCallback:Enabled context");
+ }
+ },
+ new AfterEachCallback() {
+ @Override
+ public void afterEach(ExtensionContext extensionContext) {
+ System.out.println("AfterEachCallback:Enabled context");
+ }
+ });
+ }
+ };
+ }
+}
diff --git a/testing-modules/junit5-annotations/src/test/resources/application.properties b/testing-modules/junit5-annotations/src/test/resources/application.properties
new file mode 100644
index 0000000000..bbfa14d866
--- /dev/null
+++ b/testing-modules/junit5-annotations/src/test/resources/application.properties
@@ -0,0 +1 @@
+env=qa
\ No newline at end of file
diff --git a/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/additionalanswers/BookRepository.java b/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/additionalanswers/BookRepository.java
index 78187e3f01..677ee502b4 100644
--- a/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/additionalanswers/BookRepository.java
+++ b/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/additionalanswers/BookRepository.java
@@ -1,5 +1,9 @@
package com.baeldung.mockito.additionalanswers;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
public class BookRepository {
public Book getByBookId(Long bookId) {
return new Book(bookId, "To Kill a Mocking Bird", "Harper Lee", 256);
@@ -9,9 +13,12 @@ public class BookRepository {
return new Book(book.getBookId(), book.getTitle(), book.getAuthor(), book.getNumberOfPages());
}
- public Book checkIfEquals(Book bookOne, Book bookTwo, Book bookThree) {
- if (bookOne.equals(bookTwo) && bookTwo.equals(bookThree) && bookThree.equals(bookOne)) {
- return bookOne;
- } else return bookTwo;
+ public Book selectRandomBook(Book bookOne, Book bookTwo, Book bookThree) {
+ List selection = new ArrayList<>();
+ selection.add(bookOne);
+ selection.add(bookTwo);
+ selection.add(bookThree);
+ Random random = new Random();
+ return selection.get(random.nextInt(selection.size()));
}
}
diff --git a/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/additionalanswers/BookService.java b/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/additionalanswers/BookService.java
index 92c01f8a70..4499a6524b 100644
--- a/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/additionalanswers/BookService.java
+++ b/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/additionalanswers/BookService.java
@@ -15,8 +15,8 @@ public class BookService {
return bookRepository.save(book);
}
- public Book checkifEquals(Book book1, Book book2, Book book3) {
- return bookRepository.checkIfEquals(book1, book2, book3);
+ public Book selectRandomBook(Book book1, Book book2, Book book3) {
+ return bookRepository.selectRandomBook(book1, book2, book3);
}
}
diff --git a/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/additionalanswers/BookServiceUnitTest.java b/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/additionalanswers/BookServiceUnitTest.java
index c9527ec0ec..ee32bcf70c 100644
--- a/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/additionalanswers/BookServiceUnitTest.java
+++ b/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/additionalanswers/BookServiceUnitTest.java
@@ -34,9 +34,9 @@ public class BookServiceUnitTest {
Book book2 = new Book(2L, "Animal Farm", "George Orwell", 300);
Book book3 = new Book(3L, "Romeo and Juliet", "William Shakespeare", 200);
- Mockito.when(bookRepository.checkIfEquals(any(Book.class), any(Book.class), any(Book.class))).then(AdditionalAnswers.returnsSecondArg());
+ Mockito.when(bookRepository.selectRandomBook(any(Book.class), any(Book.class), any(Book.class))).then(AdditionalAnswers.returnsSecondArg());
- Book secondBook = bookService.checkifEquals(book1, book2, book3);
+ Book secondBook = bookService.selectRandomBook(book1, book2, book3);
assertEquals(secondBook, book2);
}
@@ -47,9 +47,9 @@ public class BookServiceUnitTest {
Book book2 = new Book(2L, "Animal Farm", "George Orwell", 300);
Book book3 = new Book(3L, "Romeo and Juliet", "William Shakespeare", 200);
- Mockito.when(bookRepository.checkIfEquals(any(Book.class), any(Book.class), any(Book.class))).then(AdditionalAnswers.returnsLastArg());
+ Mockito.when(bookRepository.selectRandomBook(any(Book.class), any(Book.class), any(Book.class))).then(AdditionalAnswers.returnsLastArg());
- Book lastBook = bookService.checkifEquals(book1, book2, book3);
+ Book lastBook = bookService.selectRandomBook(book1, book2, book3);
assertEquals(lastBook, book3);
}
@@ -59,9 +59,9 @@ public class BookServiceUnitTest {
Book book2 = new Book(2L, "Animal Farm", "George Orwell", 300);
Book book3 = new Book(3L, "Romeo and Juliet", "William Shakespeare", 200);
- Mockito.when(bookRepository.checkIfEquals(any(Book.class), any(Book.class), any(Book.class))).then(AdditionalAnswers.returnsArgAt(1));
+ Mockito.when(bookRepository.selectRandomBook(any(Book.class), any(Book.class), any(Book.class))).then(AdditionalAnswers.returnsArgAt(1));
- Book bookOnIndex = bookService.checkifEquals(book1, book2, book3);
+ Book bookOnIndex = bookService.selectRandomBook(book1, book2, book3);
assertEquals(bookOnIndex, book2);
}