diff --git a/core-java-modules/core-java-concurrency-advanced-4/src/main/java/com/baeldung/lockbykey/ExampleUsage.java b/core-java-modules/core-java-concurrency-advanced-4/src/main/java/com/baeldung/lockbykey/ExampleUsage.java new file mode 100644 index 0000000000..23cfb8455a --- /dev/null +++ b/core-java-modules/core-java-concurrency-advanced-4/src/main/java/com/baeldung/lockbykey/ExampleUsage.java @@ -0,0 +1,43 @@ +package com.baeldung.lockbykey; + +/** + * This class shows examples of how you should use the lock + * + */ +public class ExampleUsage { + + void doWithSimpleExclusiveLock(String key) { + SimpleExclusiveLockByKey simpleExclusiveLockByKey = new SimpleExclusiveLockByKey(); + if (simpleExclusiveLockByKey.tryLock(key)) { + try { + // do stuff + } finally { + // it is very important to unlock in the finally block to avoid locking keys forever + simpleExclusiveLockByKey.unlock(key); + } + } + } + + // A concrete example can be found in the unit tests + void doWithLock(String key) { + LockByKey lockByKey = new LockByKey(); + lockByKey.lock(key); + try { + // do stuff + } finally { + lockByKey.unlock(key); + } + } + + // It works exactly the same as with locks + void doWithSemaphore(String key) { + SimultaneousEntriesLockByKey lockByKey = new SimultaneousEntriesLockByKey(); + lockByKey.lock(key); + try { + // do stuff + } finally { + lockByKey.unlock(key); + } + } + +} diff --git a/core-java-modules/core-java-concurrency-advanced-4/src/main/java/com/baeldung/lockbykey/LockByKey.java b/core-java-modules/core-java-concurrency-advanced-4/src/main/java/com/baeldung/lockbykey/LockByKey.java new file mode 100644 index 0000000000..f81aa6779e --- /dev/null +++ b/core-java-modules/core-java-concurrency-advanced-4/src/main/java/com/baeldung/lockbykey/LockByKey.java @@ -0,0 +1,41 @@ +package com.baeldung.lockbykey; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class LockByKey { + + private static class LockWrapper { + private final Lock lock = new ReentrantLock(); + private final AtomicInteger numberOfThreadsInQueue = new AtomicInteger(1); + + private LockWrapper addThreadInQueue() { + numberOfThreadsInQueue.incrementAndGet(); + return this; + } + + private int removeThreadFromQueue() { + return numberOfThreadsInQueue.decrementAndGet(); + } + + } + + private static ConcurrentHashMap locks = new ConcurrentHashMap(); + + public void lock(String key) { + LockWrapper lockWrapper = locks.compute(key, (k, v) -> v == null ? new LockWrapper() : v.addThreadInQueue()); + lockWrapper.lock.lock(); + } + + public void unlock(String key) { + LockWrapper lockWrapper = locks.get(key); + lockWrapper.lock.unlock(); + if (lockWrapper.removeThreadFromQueue() == 0) { + // NB : We pass in the specific value to remove to handle the case where another thread would queue right before the removal + locks.remove(key, lockWrapper); + } + } + +} diff --git a/core-java-modules/core-java-concurrency-advanced-4/src/main/java/com/baeldung/lockbykey/SimpleExclusiveLockByKey.java b/core-java-modules/core-java-concurrency-advanced-4/src/main/java/com/baeldung/lockbykey/SimpleExclusiveLockByKey.java new file mode 100644 index 0000000000..9182f9c038 --- /dev/null +++ b/core-java-modules/core-java-concurrency-advanced-4/src/main/java/com/baeldung/lockbykey/SimpleExclusiveLockByKey.java @@ -0,0 +1,18 @@ +package com.baeldung.lockbykey; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class SimpleExclusiveLockByKey { + + private static Set usedKeys= ConcurrentHashMap.newKeySet(); + + public boolean tryLock(String key) { + return usedKeys.add(key); + } + + public void unlock(String key) { + usedKeys.remove(key); + } + +} diff --git a/core-java-modules/core-java-concurrency-advanced-4/src/main/java/com/baeldung/lockbykey/SimultaneousEntriesLockByKey.java b/core-java-modules/core-java-concurrency-advanced-4/src/main/java/com/baeldung/lockbykey/SimultaneousEntriesLockByKey.java new file mode 100644 index 0000000000..9532f973fa --- /dev/null +++ b/core-java-modules/core-java-concurrency-advanced-4/src/main/java/com/baeldung/lockbykey/SimultaneousEntriesLockByKey.java @@ -0,0 +1,25 @@ +package com.baeldung.lockbykey; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; + +public class SimultaneousEntriesLockByKey { + + private static final int ALLOWED_THREADS = 2; + + private static ConcurrentHashMap semaphores = new ConcurrentHashMap(); + + public void lock(String key) { + Semaphore semaphore = semaphores.compute(key, (k, v) -> v == null ? new Semaphore(ALLOWED_THREADS) : v); + semaphore.acquireUninterruptibly(); + } + + public void unlock(String key) { + Semaphore semaphore = semaphores.get(key); + semaphore.release(); + if (semaphore.availablePermits() == ALLOWED_THREADS) { + semaphores.remove(key, semaphore); + } + } + +} diff --git a/core-java-modules/core-java-concurrency-advanced-4/src/test/java/com/baeldung/lockbykey/LockByKeyUnitTest.java b/core-java-modules/core-java-concurrency-advanced-4/src/test/java/com/baeldung/lockbykey/LockByKeyUnitTest.java new file mode 100644 index 0000000000..4e43a8fb49 --- /dev/null +++ b/core-java-modules/core-java-concurrency-advanced-4/src/test/java/com/baeldung/lockbykey/LockByKeyUnitTest.java @@ -0,0 +1,106 @@ +package com.baeldung.lockbykey; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.Test; + +public class LockByKeyUnitTest { + + @Test + void givenNoLockedKey_WhenLock_ThenSuccess() throws InterruptedException { + AtomicBoolean threadWasExecuted = new AtomicBoolean(false); + Thread thread = new Thread(() -> { + String key = "key"; + LockByKey lockByKey = new LockByKey(); + lockByKey.lock(key); + try { + threadWasExecuted.set(true); + } finally { + lockByKey.unlock(key); + } + }); + try { + thread.start(); + Thread.sleep(100); + } finally { + assertTrue(threadWasExecuted.get()); + } + } + + @Test + void givenLockedKey_WhenLock_ThenFailure() throws InterruptedException { + String key = "key"; + LockByKey lockByKey = new LockByKey(); + lockByKey.lock(key); + AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false); + Thread threadLockingOnAnotherKey = new Thread(() -> { + LockByKey otherLockByKey = new LockByKey(); + otherLockByKey.lock(key); + try { + anotherThreadWasExecuted.set(true); + } finally { + otherLockByKey.unlock(key); + } + }); + try { + threadLockingOnAnotherKey.start(); + Thread.sleep(100); + } finally { + assertFalse(anotherThreadWasExecuted.get()); + lockByKey.unlock(key); + } + } + + @Test + void givenAnotherKeyLocked_WhenLock_ThenSuccess() throws InterruptedException { + String key = "key"; + LockByKey lockByKey = new LockByKey(); + lockByKey.lock(key); + AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false); + Thread threadLockingOnAnotherKey = new Thread(() -> { + String anotherKey = "anotherKey"; + LockByKey otherLockByKey = new LockByKey(); + otherLockByKey.lock(anotherKey); + try { + anotherThreadWasExecuted.set(true); + } finally { + otherLockByKey.unlock(anotherKey); + } + }); + try { + threadLockingOnAnotherKey.start(); + Thread.sleep(100); + } finally { + assertTrue(anotherThreadWasExecuted.get()); + lockByKey.unlock(key); + } + } + + @Test + void givenUnlockedKey_WhenLock_ThenSuccess() throws InterruptedException { + String key = "key"; + LockByKey lockByKey = new LockByKey(); + lockByKey.lock(key); + AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false); + Thread threadLockingOnAnotherKey = new Thread(() -> { + LockByKey otherLockByKey = new LockByKey(); + otherLockByKey.lock(key); + try { + anotherThreadWasExecuted.set(true); + } finally { + otherLockByKey.unlock(key); + } + }); + try { + lockByKey.unlock(key); + threadLockingOnAnotherKey.start(); + Thread.sleep(100); + } finally { + assertTrue(anotherThreadWasExecuted.get()); + } + } + +} diff --git a/core-java-modules/core-java-concurrency-advanced-4/src/test/java/com/baeldung/lockbykey/SimpleExclusiveLockByKeyUnitTest.java b/core-java-modules/core-java-concurrency-advanced-4/src/test/java/com/baeldung/lockbykey/SimpleExclusiveLockByKeyUnitTest.java new file mode 100644 index 0000000000..deba728664 --- /dev/null +++ b/core-java-modules/core-java-concurrency-advanced-4/src/test/java/com/baeldung/lockbykey/SimpleExclusiveLockByKeyUnitTest.java @@ -0,0 +1,51 @@ +package com.baeldung.lockbykey; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Field; +import java.util.concurrent.ConcurrentHashMap; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SimpleExclusiveLockByKeyUnitTest { + + @BeforeEach + void cleanUpLocks() throws Exception { + Field field = SimpleExclusiveLockByKey.class.getDeclaredField("usedKeys"); + field.setAccessible(true); + field.set(null, ConcurrentHashMap.newKeySet()); + } + + @Test + void givenNoLockedKey_WhenTryLock_ThenSuccess() { + SimpleExclusiveLockByKey lockByKey = new SimpleExclusiveLockByKey(); + assertTrue(lockByKey.tryLock("key")); + } + + @Test + void givenLockedKey_WhenTryLock_ThenFailure() { + String key = "key"; + SimpleExclusiveLockByKey lockByKey = new SimpleExclusiveLockByKey(); + lockByKey.tryLock(key); + assertFalse(lockByKey.tryLock(key)); + } + + @Test + void givenAnotherKeyLocked_WhenTryLock_ThenSuccess() { + SimpleExclusiveLockByKey lockByKey = new SimpleExclusiveLockByKey(); + lockByKey.tryLock("other"); + assertTrue(lockByKey.tryLock("key")); + } + + @Test + void givenUnlockedKey_WhenTryLock_ThenSuccess() { + String key = "key"; + SimpleExclusiveLockByKey lockByKey = new SimpleExclusiveLockByKey(); + lockByKey.tryLock(key); + lockByKey.unlock(key); + assertTrue(lockByKey.tryLock(key)); + } + +} diff --git a/core-java-modules/core-java-concurrency-advanced-4/src/test/java/com/baeldung/lockbykey/SimultaneousEntriesLockByKeyUnitTest.java b/core-java-modules/core-java-concurrency-advanced-4/src/test/java/com/baeldung/lockbykey/SimultaneousEntriesLockByKeyUnitTest.java new file mode 100644 index 0000000000..ec4e7f4d80 --- /dev/null +++ b/core-java-modules/core-java-concurrency-advanced-4/src/test/java/com/baeldung/lockbykey/SimultaneousEntriesLockByKeyUnitTest.java @@ -0,0 +1,146 @@ +package com.baeldung.lockbykey; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.Test; + +public class SimultaneousEntriesLockByKeyUnitTest { + + @Test + void givenNoKeyUsed_WhenLock_ThenSuccess() throws InterruptedException { + AtomicBoolean threadWasExecuted = new AtomicBoolean(false); + Thread thread = new Thread(() -> { + String key = "key"; + SimultaneousEntriesLockByKey lockByKey = new SimultaneousEntriesLockByKey(); + lockByKey.lock(key); + try { + threadWasExecuted.set(true); + } finally { + lockByKey.unlock(key); + } + }); + try { + thread.start(); + Thread.sleep(100); + } finally { + assertTrue(threadWasExecuted.get()); + } + } + + @Test + void givenKeyLockedWithRemainingPermits_WhenLock_ThenSuccess() throws InterruptedException { + String key = "key"; + SimultaneousEntriesLockByKey lockByKey = new SimultaneousEntriesLockByKey(); + lockByKey.lock(key); + AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false); + Thread threadLockingOnAnotherKey = new Thread(() -> { + SimultaneousEntriesLockByKey otherLockByKeyWithSemaphore = new SimultaneousEntriesLockByKey(); + otherLockByKeyWithSemaphore.lock(key); + try { + anotherThreadWasExecuted.set(true); + } finally { + otherLockByKeyWithSemaphore.unlock(key); + } + }); + try { + threadLockingOnAnotherKey.start(); + Thread.sleep(100); + } finally { + assertTrue(anotherThreadWasExecuted.get()); + lockByKey.unlock(key); + } + } + + @Test + void givenKeyLockedWithNoRemainingPermits_WhenLock_ThenFailure() throws InterruptedException { + String key = "key"; + SimultaneousEntriesLockByKey lockByKey = new SimultaneousEntriesLockByKey(); + lockByKey.lock(key); + AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false); + Thread threadLockingOnAnotherKey1 = new Thread(() -> { + SimultaneousEntriesLockByKey otherLockByKeyWithSemaphore = new SimultaneousEntriesLockByKey(); + otherLockByKeyWithSemaphore.lock(key); + try { + Thread.sleep(200); // make sure this thread will release the lock after the assertion + } catch (InterruptedException e) { + + } finally { + otherLockByKeyWithSemaphore.unlock(key); + } + }); + Thread threadLockingOnAnotherKey2 = new Thread(() -> { + SimultaneousEntriesLockByKey otherLockByKey = new SimultaneousEntriesLockByKey(); + try { + Thread.sleep(50); // make sure thread1 will acquire the key first + } catch (InterruptedException e) { + } + otherLockByKey.lock(key); + try { + anotherThreadWasExecuted.set(true); + } finally { + otherLockByKey.unlock(key); + } + }); + try { + threadLockingOnAnotherKey1.start(); + threadLockingOnAnotherKey2.start(); + Thread.sleep(100); + } finally { + assertFalse(anotherThreadWasExecuted.get()); + lockByKey.unlock(key); + } + } + + @Test + void givenAnotherKeyLocked_WhenLock_ThenSuccess() throws InterruptedException { + String key = "key"; + SimultaneousEntriesLockByKey lockByKey = new SimultaneousEntriesLockByKey(); + lockByKey.lock(key); + AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false); + Thread threadLockingOnAnotherKey = new Thread(() -> { + String anotherKey = "anotherKey"; + SimultaneousEntriesLockByKey otherLockByKey = new SimultaneousEntriesLockByKey(); + otherLockByKey.lock(anotherKey); + try { + anotherThreadWasExecuted.set(true); + } finally { + otherLockByKey.unlock(anotherKey); + } + }); + try { + threadLockingOnAnotherKey.start(); + Thread.sleep(100); + } finally { + assertTrue(anotherThreadWasExecuted.get()); + lockByKey.unlock(key); + } + } + + @Test + void givenUnlockedKey_WhenLock_ThenSuccess() throws InterruptedException { + String key = "key"; + SimultaneousEntriesLockByKey lockByKey = new SimultaneousEntriesLockByKey(); + lockByKey.lock(key); + AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false); + Thread threadLockingOnAnotherKey = new Thread(() -> { + SimultaneousEntriesLockByKey otherLockByKey = new SimultaneousEntriesLockByKey(); + otherLockByKey.lock(key); + try { + anotherThreadWasExecuted.set(true); + } finally { + otherLockByKey.unlock(key); + } + }); + try { + lockByKey.unlock(key); + threadLockingOnAnotherKey.start(); + Thread.sleep(100); + } finally { + assertTrue(anotherThreadWasExecuted.get()); + } + } + +} diff --git a/maven-modules/pom.xml b/maven-modules/pom.xml index 37be581804..0d65e5f9f4 100644 --- a/maven-modules/pom.xml +++ b/maven-modules/pom.xml @@ -19,6 +19,7 @@ maven-copy-files maven-custom-plugin maven-exec-plugin + maven-integration-test maven-multi-source maven-plugins diff --git a/persistence-modules/hibernate-mapping-2/pom.xml b/persistence-modules/hibernate-mapping-2/pom.xml index 10c07c95eb..06a1535442 100644 --- a/persistence-modules/hibernate-mapping-2/pom.xml +++ b/persistence-modules/hibernate-mapping-2/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - hibernate-mapping-2y + hibernate-mapping-2 0.1-SNAPSHOT hibernate-mapping-2 diff --git a/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationJavaConfigMainIntegrationTest.java b/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationJavaConfigMainIntegrationTest.java index 19d1a5ff50..69b791b4d4 100644 --- a/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationJavaConfigMainIntegrationTest.java +++ b/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationJavaConfigMainIntegrationTest.java @@ -45,6 +45,13 @@ public class HibernateManyToManyAnnotationJavaConfigMainIntegrationTest { projects.add(new Project("Networking Project")); session.persist(new Employee("Peter", "Oven", projects)); session.persist(new Employee("Allan", "Norman", projects)); + + Set employees = new HashSet(); + employees.add(new Employee("Sam", "Curran")); + employees.add(new Employee("Tom", "Curran")); + Project project = new Project("Database Project"); + project.setEmployees(employees); + session.persist(project); } } diff --git a/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationMainIntegrationTest.java b/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationMainIntegrationTest.java index bfa158d43f..5255cb040f 100644 --- a/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationMainIntegrationTest.java +++ b/persistence-modules/hibernate-mapping-2/src/test/java/com/baeldung/hibernate/manytomany/HibernateManyToManyAnnotationMainIntegrationTest.java @@ -1,7 +1,7 @@ package com.baeldung.hibernate.manytomany; -import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.HashSet; import java.util.List; @@ -23,60 +23,73 @@ import com.baeldung.manytomany.util.HibernateUtil; * Configured in: manytomany.cfg.xml */ public class HibernateManyToManyAnnotationMainIntegrationTest { - private static SessionFactory sessionFactory; + private static SessionFactory sessionFactory; - private Session session; + private Session session; - @BeforeClass - public static void beforeTests() { - sessionFactory = HibernateUtil.getSessionFactory(); - } + @BeforeClass + public static void beforeTests() { + sessionFactory = HibernateUtil.getSessionFactory(); + } - @Before - public void setUp() { - session = sessionFactory.openSession(); - session.beginTransaction(); - } + @Before + public void setUp() { + session = sessionFactory.openSession(); + session.beginTransaction(); + } - @Test - public void givenData_whenInsert_thenCreatesMtoMrelationship() { - String[] employeeData = { "Peter Oven", "Allan Norman" }; - String[] projectData = { "IT Project", "Networking Project" }; - Set projects = new HashSet(); - - for (String proj : projectData) { - projects.add(new Project(proj)); - } - - for (String emp : employeeData) { - Employee employee = new Employee(emp.split(" ")[0], emp.split(" ")[1]); - assertEquals(0, employee.getProjects().size()); - employee.setProjects(projects); - session.persist(employee); - assertNotNull(employee); - } - } - - @Test + @Test public void givenSession_whenRead_thenReturnsMtoMdata() { + prepareData(); + @SuppressWarnings("unchecked") + List employeeList = session.createQuery("FROM Employee").list(); @SuppressWarnings("unchecked") - List employeeList = session.createQuery("FROM Employee").list(); + List projectList = session.createQuery("FROM Project").list(); assertNotNull(employeeList); + assertNotNull(projectList); + assertEquals(2, employeeList.size()); + assertEquals(2, projectList.size()); + for(Employee employee : employeeList) { assertNotNull(employee.getProjects()); + assertEquals(2, employee.getProjects().size()); + } + for(Project project : projectList) { + assertNotNull(project.getEmployees()); + assertEquals(2, project.getEmployees().size()); } } - @After - public void tearDown() { - session.getTransaction() - .commit(); - session.close(); - } + private void prepareData() { + String[] employeeData = { "Peter Oven", "Allan Norman" }; + String[] projectData = { "IT Project", "Networking Project" }; + Set projects = new HashSet(); - @AfterClass - public static void afterTests() { - sessionFactory.close(); - } + for (String proj : projectData) { + projects.add(new Project(proj)); + } + + for (String emp : employeeData) { + Employee employee = new Employee(emp.split(" ")[0], emp.split(" ")[1]); + employee.setProjects(projects); + + for (Project proj : projects) { + proj.getEmployees().add(employee); + } + + session.persist(employee); + } + } + + @After + public void tearDown() { + session.getTransaction().commit(); + session.close(); + } + + @AfterClass + public static void afterTests() { + sessionFactory.close(); + } } diff --git a/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/PushOperations.java b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/PushOperations.java new file mode 100644 index 0000000000..fa1f9ddc96 --- /dev/null +++ b/persistence-modules/java-mongodb/src/main/java/com/baeldung/mongo/PushOperations.java @@ -0,0 +1,99 @@ +package com.baeldung.mongo; + +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.bson.Document; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Updates; +import com.mongodb.client.result.UpdateResult; + +public class PushOperations { + + private static MongoClient mongoClient; + private static String testCollectionName; + private static String databaseName; + + public static void setUp() { + if (mongoClient == null) { + mongoClient = new MongoClient("localhost", 27017); + } + + databaseName = "baeldung"; + testCollectionName = "orders"; + + } + + public static void pushOperationUsingDBObject() { + + MongoDatabase database = mongoClient.getDatabase(databaseName); + MongoCollection collection = database.getCollection(testCollectionName); + DBObject listItem = new BasicDBObject("items", new BasicDBObject("itemName", "PIZZA MANIA").append("quantity", 1) + .append("price", 800)); + BasicDBObject searchFilter = new BasicDBObject("customerId", 1023); + BasicDBObject updateQuery = new BasicDBObject(); + updateQuery.append("$push", listItem); + UpdateResult updateResult = collection.updateOne(searchFilter, updateQuery); + + System.out.println("updateResult:- " + updateResult); + } + + public static void pushOperationUsingDocument() { + + MongoDatabase database = mongoClient.getDatabase(databaseName); + MongoCollection collection = database.getCollection(testCollectionName); + + Document item = new Document().append("itemName", "PIZZA MANIA") + .append("quantity", 1) + .append("price", 800); + UpdateResult updateResult = collection.updateOne(Filters.eq("customerId", 1023), Updates.push("items", item)); + + System.out.println("updateResult:- " + updateResult); + } + + public static void addToSetOperation() { + + MongoDatabase database = mongoClient.getDatabase(databaseName); + MongoCollection collection = database.getCollection(testCollectionName); + + Document item = new Document().append("itemName", "PIZZA MANIA") + .append("quantity", 1) + .append("price", 800); + UpdateResult updateResult = collection.updateOne(Filters.eq("customerId", 1023), Updates.addToSet("items", item)); + System.out.println("updateResult:- " + updateResult); + } + + public static void main(String args[]) { + + // + // Connect to cluster (default is localhost:27017) + // + setUp(); + + // + // Push document into the array using DBObject + // + + pushOperationUsingDBObject(); + + // + // Push document into the array using Document. + // + + pushOperationUsingDocument(); + + // + // Push document into the array using addToSet operator. + // + addToSetOperation(); + + } + +} + diff --git a/persistence-modules/java-mongodb/src/test/java/com/baeldung/mongo/PushOperationLiveTest.java b/persistence-modules/java-mongodb/src/test/java/com/baeldung/mongo/PushOperationLiveTest.java new file mode 100644 index 0000000000..bd8523b301 --- /dev/null +++ b/persistence-modules/java-mongodb/src/test/java/com/baeldung/mongo/PushOperationLiveTest.java @@ -0,0 +1,92 @@ +package com.baeldung.mongo; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +import org.bson.Document; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Updates; +import com.mongodb.client.result.UpdateResult; + +public class PushOperationLiveTest { + + private static MongoClient mongoClient; + private static MongoDatabase db; + private static MongoCollection collection; + + @BeforeClass + public static void setup() { + if (mongoClient == null) { + mongoClient = new MongoClient("localhost", 27017); + db = mongoClient.getDatabase("baeldung"); + collection = db.getCollection("orders"); + + collection.insertOne( + Document.parse("{\n" + " \"customerId\": 1023,\n" + " \"orderTimestamp\": NumberLong(\"1646460073000\"),\n" + " \"shippingDestination\": \"336, Street No.1 Pawai Mumbai\",\n" + " \"purchaseOrder\": 1000,\n" + + " \"contactNumber\":\"9898987676\",\n" + " \"items\": [ \n" + " {\n" + " \"itemName\": \"BERGER\",\n" + " \"quantity\": 1,\n" + " \"price\": 500\n" + " },\n" + + " {\n" + " \"itemName\": \"VEG PIZZA\",\n" + " \"quantity\": 1,\n" + " \"price\": 800\n" + " } \n" + " ]\n" + " }")); + } + } + + @Test + public void givenOrderCollection_whenPushOperationUsingDBObject_thenCheckingForDocument() { + + DBObject listItem = new BasicDBObject("items", new BasicDBObject("itemName", "PIZZA MANIA").append("quantity", 1) + .append("price", 800)); + BasicDBObject searchFilter = new BasicDBObject("customerId", 1023); + BasicDBObject updateQuery = new BasicDBObject(); + updateQuery.append("$push", listItem); + UpdateResult updateResult = collection.updateOne(searchFilter, updateQuery); + + Document orderDetail = collection.find(Filters.eq("customerId", 1023)) + .first(); + assertNotNull(orderDetail); + assertFalse(orderDetail.isEmpty()); + + } + + @Test + public void givenOrderCollection_whenPushOperationUsingDocument_thenCheckingForDocument() { + + Document item = new Document().append("itemName", "PIZZA MANIA") + .append("quantity", 1) + .append("price", 800); + UpdateResult updateResult = collection.updateOne(Filters.eq("customerId", 1023), Updates.push("items", item)); + + Document orderDetail = collection.find(Filters.eq("customerId", 1023)) + .first(); + assertNotNull(orderDetail); + assertFalse(orderDetail.isEmpty()); + + } + + @Test + public void givenOrderCollection_whenAddToSetOperation_thenCheckingForDocument() { + + Document item = new Document().append("itemName", "PIZZA MANIA") + .append("quantity", 1) + .append("price", 800); + UpdateResult updateResult = collection.updateOne(Filters.eq("customerId", 1023), Updates.addToSet("items", item)); + + Document orderDetail = collection.find(Filters.eq("customerId", 1023)) + .first(); + assertNotNull(orderDetail); + assertFalse(orderDetail.isEmpty()); + } + + @AfterClass + public static void cleanUp() { + mongoClient.close(); + } + +} + diff --git a/persistence-modules/pom.xml b/persistence-modules/pom.xml index 152e58d57b..64a9519a8b 100644 --- a/persistence-modules/pom.xml +++ b/persistence-modules/pom.xml @@ -27,6 +27,7 @@ hbase hibernate5 hibernate-mapping + hibernate-mapping-2 hibernate-ogm hibernate-annotations hibernate-exceptions @@ -73,6 +74,7 @@ spring-data-jpa-crud spring-data-jpa-crud-2 spring-data-jpa-enterprise + spring-data-jpa-enterprise-2 spring-data-jpa-filtering spring-data-jpa-query spring-data-jpa-query-2 diff --git a/pom.xml b/pom.xml index b6767c629b..a6202230d4 100644 --- a/pom.xml +++ b/pom.xml @@ -7,9 +7,6 @@ com.baeldung parent-modules 1.0.0-SNAPSHOT - - spring-5-webflux-2 - parent-modules pom @@ -355,6 +352,7 @@ apache-libraries apache-olingo apache-poi + apache-poi-2 apache-rocketmq apache-shiro apache-spark @@ -653,6 +651,7 @@ spring-cucumber spring-data-rest + spring-data-rest-2 spring-data-rest-querydsl spring-di spring-di-2 @@ -841,6 +840,7 @@ apache-libraries apache-olingo apache-poi + apache-poi-2 apache-rocketmq apache-shiro apache-spark @@ -855,6 +855,7 @@ atomix aws + aws-app-sync aws-lambda aws-reactive @@ -906,6 +907,7 @@ graphql/graphql-java + graphql/graphql-dgs grpc gson guava-modules @@ -967,6 +969,7 @@ ksqldb + language-interop libraries-2 libraries-3 @@ -981,6 +984,7 @@ libraries-http-2 libraries-io libraries-primitive + libraries-rpc libraries-security libraries-server libraries-server-2 @@ -1096,6 +1100,7 @@ spring-5-reactive-oauth spring-5-reactive-security spring-5-webflux + spring-5-webflux-2 spring-reactive spring-activiti @@ -1128,9 +1133,11 @@ spring-cucumber spring-data-rest + spring-data-rest-2 spring-data-rest-querydsl spring-di spring-di-2 + spring-di-3 spring-drools spring-ejb @@ -1168,6 +1175,8 @@ spring-static-resources spring-swagger-codegen + spring-threads + spring-vault spring-vertx @@ -1317,39 +1326,43 @@ core-java-modules/core-java-9-improvements core-java-modules/core-java-9-jigsaw - core-java-modules/core-java-9-streams - core-java-modules/core-java-10 - core-java-modules/core-java-11 - core-java-modules/core-java-11-2 - - - - - core-java-modules/core-java-collections-set - core-java-modules/core-java-collections-maps-4 - core-java-modules/core-java-date-operations-1 - core-java-modules/core-java-datetime-conversion - core-java-modules/core-java-datetime-string - core-java-modules/core-java-io-conversions-2 - core-java-modules/core-java-jpms - core-java-modules/core-java-os - core-java-modules/core-java-string-algorithms-3 - core-java-modules/core-java-string-operations-3 - core-java-modules/core-java-string-operations-4 - core-java-modules/core-java-time-measurements - core-java-modules/core-java-networking-3 - core-java-modules/multimodulemavenproject - ddd-modules - httpclient-2 - libraries-concurrency - persistence-modules/sirix - persistence-modules/spring-data-cassandra-2 - quarkus-vs-springboot - quarkus-jandex - spring-boot-modules/spring-boot-cassandre - spring-boot-modules/spring-boot-camel - testing-modules/testing-assertions - persistence-modules/fauna + core-java-modules/core-java-9-streams + core-java-modules/core-java-10 + core-java-modules/core-java-11 + core-java-modules/core-java-11-2 + + + + + + + core-java-modules/core-java-collections-set + core-java-modules/core-java-collections-maps-4 + core-java-modules/core-java-date-operations-1 + core-java-modules/core-java-datetime-conversion + core-java-modules/core-java-datetime-string + core-java-modules/core-java-io-conversions-2 + core-java-modules/core-java-jpms + core-java-modules/core-java-os + core-java-modules/core-java-string-algorithms-3 + core-java-modules/core-java-string-operations-3 + core-java-modules/core-java-string-operations-4 + core-java-modules/core-java-time-measurements + core-java-modules/core-java-networking-3 + core-java-modules/multimodulemavenproject + core-java-modules/core-java-strings + ddd-modules + docker + httpclient-2 + libraries-concurrency + persistence-modules/sirix + persistence-modules/spring-data-cassandra-2 + quarkus-vs-springboot + quarkus-jandex + spring-boot-modules/spring-boot-cassandre + spring-boot-modules/spring-boot-camel + testing-modules/testing-assertions + persistence-modules/fauna @@ -1388,6 +1401,8 @@ + + core-java-modules/core-java-collections-set core-java-modules/core-java-collections-maps-4 core-java-modules/core-java-date-operations-1 @@ -1396,6 +1411,7 @@ core-java-modules/core-java-io-conversions-2 core-java-modules/core-java-jpms core-java-modules/core-java-os + core-java-modules/core-java-string-algorithms-3 core-java-modules/core-java-string-operations-3 core-java-modules/core-java-string-operations-4 core-java-modules/core-java-time-measurements @@ -1403,6 +1419,7 @@ core-java-modules/multimodulemavenproject core-java-modules/core-java-strings ddd-modules + docker httpclient-2 libraries-concurrency persistence-modules/sirix diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/KeycloakConfig.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/KeycloakConfig.java new file mode 100644 index 0000000000..6a3dc45717 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/KeycloakConfig.java @@ -0,0 +1,14 @@ +package com.baeldung.keycloak; + +import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class KeycloakConfig { + + @Bean + public KeycloakSpringBootConfigResolver keycloakConfigResolver() { + return new KeycloakSpringBootConfigResolver(); + } +} diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/SecurityConfig.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/SecurityConfig.java index 78023aff8f..826f475a6e 100644 --- a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/SecurityConfig.java +++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/SecurityConfig.java @@ -23,11 +23,6 @@ class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { auth.authenticationProvider(keycloakAuthenticationProvider); } - @Bean - public KeycloakSpringBootConfigResolver KeycloakConfigResolver() { - return new KeycloakSpringBootConfigResolver(); - } - // Specifies the session authentication strategy @Bean @Override diff --git a/spring-cloud/pom.xml b/spring-cloud/pom.xml index ac810c9a77..75010fbd34 100644 --- a/spring-cloud/pom.xml +++ b/spring-cloud/pom.xml @@ -45,9 +45,11 @@ spring-cloud-ribbon-retry spring-cloud-circuit-breaker spring-cloud-eureka-self-preservation - + + spring-cloud-sentinel spring-cloud-dapr + spring-cloud-docker diff --git a/spring-ejb/wildfly/pom.xml b/spring-ejb/wildfly/pom.xml index ae90f71b7c..8a6d41ea82 100644 --- a/spring-ejb/wildfly/pom.xml +++ b/spring-ejb/wildfly/pom.xml @@ -21,6 +21,7 @@ wildfly-jpa wildfly-ejb-interfaces wildfly-ejb + wildfly-mdb diff --git a/spring-security-modules/spring-security-web-boot-3/pom.xml b/spring-security-modules/spring-security-web-boot-3/pom.xml index 5f2a455294..5da993acd9 100644 --- a/spring-security-modules/spring-security-web-boot-3/pom.xml +++ b/spring-security-modules/spring-security-web-boot-3/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 spring-security-web-boot-3 0.0.1-SNAPSHOT @@ -23,6 +24,15 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-data-mongodb + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + 3.3.1 + commons-io commons-io diff --git a/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/MongoAuthApplication.java b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/MongoAuthApplication.java new file mode 100644 index 0000000000..53624c0dd8 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/MongoAuthApplication.java @@ -0,0 +1,18 @@ +package com.baeldung.mongoauth; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +import com.baeldung.mongoauth.config.MongoConfig; +import com.baeldung.mongoauth.config.SecurityConfig; + +@SpringBootApplication +@Import({ SecurityConfig.class, MongoConfig.class }) +public class MongoAuthApplication { + + public static void main(String... args) { + SpringApplication.run(MongoAuthApplication.class, args); + } + +} diff --git a/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/config/MongoConfig.java b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/config/MongoConfig.java new file mode 100644 index 0000000000..ddef7800de --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/config/MongoConfig.java @@ -0,0 +1,40 @@ +package com.baeldung.mongoauth.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.util.SocketUtils; + +import com.mongodb.client.MongoClients; + +import de.flapdoodle.embed.mongo.MongodExecutable; +import de.flapdoodle.embed.mongo.MongodStarter; +import de.flapdoodle.embed.mongo.config.ImmutableMongodConfig; +import de.flapdoodle.embed.mongo.config.MongodConfig; +import de.flapdoodle.embed.mongo.config.Net; +import de.flapdoodle.embed.mongo.distribution.Version; +import de.flapdoodle.embed.process.runtime.Network; + +@Configuration +public class MongoConfig { + + private static final String CONNECTION_STRING = "mongodb://%s:%d"; + private static final String HOST = "localhost"; + + @Bean + public MongoTemplate mongoTemplate() throws Exception { + + int randomPort = SocketUtils.findAvailableTcpPort(); + + ImmutableMongodConfig mongoDbConfig = MongodConfig.builder() + .version(Version.Main.PRODUCTION) + .net(new Net(HOST, randomPort, Network.localhostIsIPv6())) + .build(); + + MongodStarter starter = MongodStarter.getDefaultInstance(); + MongodExecutable mongodExecutable = starter.prepare(mongoDbConfig); + mongodExecutable.start(); + return new MongoTemplate(MongoClients.create(String.format(CONNECTION_STRING, HOST, randomPort)), "mongo_auth"); + } + +} diff --git a/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/config/SecurityConfig.java b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/config/SecurityConfig.java new file mode 100644 index 0000000000..050d917492 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/config/SecurityConfig.java @@ -0,0 +1,59 @@ +package com.baeldung.mongoauth.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + private final UserDetailsService userDetailsService; + + public SecurityConfig(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Bean + public AuthenticationManager customAuthenticationManager() throws Exception { + return authenticationManager(); + } + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(@Autowired AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService) + .passwordEncoder(bCryptPasswordEncoder()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf() + .disable() + .authorizeRequests() + .and() + .httpBasic() + .and() + .authorizeRequests() + .anyRequest() + .permitAll() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + } + +} diff --git a/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/controller/ResourceController.java b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/controller/ResourceController.java new file mode 100644 index 0000000000..a5d9e91083 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/controller/ResourceController.java @@ -0,0 +1,23 @@ +package com.baeldung.mongoauth.controller; + +import javax.annotation.security.RolesAllowed; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ResourceController { + + @RolesAllowed("ROLE_ADMIN") + @GetMapping("/admin") + public String admin() { + return "Hello Admin!"; + } + + @RolesAllowed({ "ROLE_ADMIN", "ROLE_USER" }) + @GetMapping("/user") + public String user() { + return "Hello User!"; + } + +} diff --git a/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/domain/Role.java b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/domain/Role.java new file mode 100644 index 0000000000..e475e68460 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/domain/Role.java @@ -0,0 +1,13 @@ +package com.baeldung.mongoauth.domain; + +public class Role { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/domain/User.java b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/domain/User.java new file mode 100644 index 0000000000..ffaea836a4 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/domain/User.java @@ -0,0 +1,83 @@ +package com.baeldung.mongoauth.domain; + +import java.util.Objects; +import java.util.Set; + +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.MongoId; +import org.springframework.security.core.userdetails.UserDetails; + +@Document +public class User implements UserDetails { + private @MongoId ObjectId id; + private String username; + private String password; + private Set userRoles; + + public ObjectId getId() { + return id; + } + + @Override + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setUserRoles(Set userRoles) { + this.userRoles = userRoles; + } + + @Override + public Set getAuthorities() { + return this.userRoles; + } + + @Override + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + @Override + public boolean isAccountNonExpired() { + return false; + } + + @Override + public boolean isAccountNonLocked() { + return false; + } + + @Override + public boolean isCredentialsNonExpired() { + return false; + } + + @Override + public boolean isEnabled() { + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + User user = (User) o; + return Objects.equals(username, user.username); + } + + @Override + public int hashCode() { + return Objects.hash(username); + } +} diff --git a/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/domain/UserRole.java b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/domain/UserRole.java new file mode 100644 index 0000000000..ccfa3bd605 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/domain/UserRole.java @@ -0,0 +1,21 @@ +package com.baeldung.mongoauth.domain; + +import org.springframework.security.core.GrantedAuthority; + +public class UserRole implements GrantedAuthority { + + private Role role; + + @Override + public String getAuthority() { + return role.getName(); + } + + public Role getRole() { + return role; + } + + public void setRole(Role role) { + this.role = role; + } +} diff --git a/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/repository/UserRepository.java b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/repository/UserRepository.java new file mode 100644 index 0000000000..c68f77ffbf --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/repository/UserRepository.java @@ -0,0 +1,13 @@ +package com.baeldung.mongoauth.repository; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; + +import com.baeldung.mongoauth.domain.User; + +public interface UserRepository extends MongoRepository { + + @Query("{username:'?0'}") + User findUserByUsername(String username); + +} diff --git a/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/service/MongoAuthUserDetailService.java b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/service/MongoAuthUserDetailService.java new file mode 100644 index 0000000000..5838504d40 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/service/MongoAuthUserDetailService.java @@ -0,0 +1,41 @@ +package com.baeldung.mongoauth.service; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import com.baeldung.mongoauth.repository.UserRepository; + +@Service +public class MongoAuthUserDetailService implements UserDetailsService { + + private final UserRepository userRepository; + + public MongoAuthUserDetailService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { + + com.baeldung.mongoauth.domain.User user = userRepository.findUserByUsername(userName); + + Set grantedAuthorities = new HashSet<>(); + + user.getAuthorities() + .forEach(role -> { + grantedAuthorities.add(new SimpleGrantedAuthority(role.getRole() + .getName())); + }); + + return new User(user.getUsername(), user.getPassword(), grantedAuthorities); + } + +} diff --git a/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/service/SecurityService.java b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/service/SecurityService.java new file mode 100644 index 0000000000..4204e4708b --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/service/SecurityService.java @@ -0,0 +1,5 @@ +package com.baeldung.mongoauth.service; + +public interface SecurityService { + boolean login(String username, String password); +} diff --git a/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/service/SecurityServiceImpl.java b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/service/SecurityServiceImpl.java new file mode 100644 index 0000000000..f86ffaa26a --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/main/java/com/baeldung/mongoauth/service/SecurityServiceImpl.java @@ -0,0 +1,39 @@ +package com.baeldung.mongoauth.service; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Service; + +@Service +public class SecurityServiceImpl implements SecurityService { + + private final AuthenticationManager authenticationManager; + + private final UserDetailsService userDetailsService; + + public SecurityServiceImpl(AuthenticationManager authenticationManager, UserDetailsService userDetailsService) { + this.authenticationManager = authenticationManager; + this.userDetailsService = userDetailsService; + } + + @Override + public boolean login(String username, String password) { + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities()); + + authenticationManager.authenticate(usernamePasswordAuthenticationToken); + + if (usernamePasswordAuthenticationToken.isAuthenticated()) { + SecurityContextHolder.getContext() + .setAuthentication(usernamePasswordAuthenticationToken); + + return true; + } + + return false; + } +} diff --git a/spring-security-modules/spring-security-web-boot-3/src/main/resources/application.properties b/spring-security-modules/spring-security-web-boot-3/src/main/resources/application.properties new file mode 100644 index 0000000000..a5b5fb9804 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.mongodb.embedded.version=4.4.9 \ No newline at end of file diff --git a/spring-security-modules/spring-security-web-boot-3/src/test/java/com/baeldung/mongoauth/MongoAuthApplicationIntegrationTest.java b/spring-security-modules/spring-security-web-boot-3/src/test/java/com/baeldung/mongoauth/MongoAuthApplicationIntegrationTest.java new file mode 100644 index 0000000000..b7994cad9e --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/test/java/com/baeldung/mongoauth/MongoAuthApplicationIntegrationTest.java @@ -0,0 +1,118 @@ +package com.baeldung.mongoauth; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.Collections; +import java.util.HashSet; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +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.data.mongodb.core.MongoTemplate; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import com.baeldung.mongoauth.domain.Role; +import com.baeldung.mongoauth.domain.User; +import com.baeldung.mongoauth.domain.UserRole; + +@SpringBootTest(classes = { MongoAuthApplication.class }) +@AutoConfigureMockMvc +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class MongoAuthApplicationIntegrationTest { + + @Autowired + private WebApplicationContext context; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private BCryptPasswordEncoder bCryptPasswordEncoder; + + private MockMvc mvc; + + private static final String USER_NAME = "user@gmail.com"; + private static final String ADMIN_NAME = "admin@gmail.com"; + private static final String PASSWORD = "password"; + + @BeforeEach + public void setup() { + + setUp(); + + mvc = MockMvcBuilders.webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + private void setUp() { + Role roleUser = new Role(); + roleUser.setName("ROLE_USER"); + mongoTemplate.save(roleUser); + + User user = new User(); + user.setUsername(USER_NAME); + user.setPassword(bCryptPasswordEncoder.encode(PASSWORD)); + + UserRole userRole = new UserRole(); + userRole.setRole(roleUser); + user.setUserRoles(new HashSet<>(Collections.singletonList(userRole))); + mongoTemplate.save(user); + + User admin = new User(); + admin.setUsername(ADMIN_NAME); + admin.setPassword(bCryptPasswordEncoder.encode(PASSWORD)); + + Role roleAdmin = new Role(); + roleAdmin.setName("ROLE_ADMIN"); + mongoTemplate.save(roleAdmin); + + UserRole adminRole = new UserRole(); + adminRole.setRole(roleAdmin); + admin.setUserRoles(new HashSet<>(Collections.singletonList(adminRole))); + mongoTemplate.save(admin); + } + + @Test + void givenUserCredentials_whenInvokeUserAuthorizedEndPoint_thenReturn200() throws Exception { + mvc.perform(get("/user").with(httpBasic(USER_NAME, PASSWORD))) + .andExpect(status().isOk()); + } + + @Test + void givenUserNotExists_whenInvokeEndPoint_thenReturn401() throws Exception { + mvc.perform(get("/user").with(httpBasic("not_existing_user", "password"))) + .andExpect(status().isUnauthorized()); + } + + @Test + void givenUserExistsAndWrongPassword_whenInvokeEndPoint_thenReturn401() throws Exception { + mvc.perform(get("/user").with(httpBasic(USER_NAME, "wrong_password"))) + .andExpect(status().isUnauthorized()); + } + + @Test + void givenUserCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn403() throws Exception { + mvc.perform(get("/admin").with(httpBasic(USER_NAME, PASSWORD))) + .andExpect(status().isForbidden()); + } + + @Test + void givenAdminCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn200() throws Exception { + mvc.perform(get("/admin").with(httpBasic(ADMIN_NAME, PASSWORD))) + .andExpect(status().isOk()); + + mvc.perform(get("/user").with(httpBasic(ADMIN_NAME, PASSWORD))) + .andExpect(status().isOk()); + } + +}