Date: Tue, 17 Aug 2021 22:05:01 +0300
Subject: [PATCH 03/25] Fix 40 errors from checkstyle plugin run. 139 left))
---
.../main/java/com/iluwatar/caching/App.java | 136 ++++++++++--------
.../java/com/iluwatar/caching/AppManager.java | 64 ++++++---
.../iluwatar/caching/database/DbManager.java | 30 +++-
.../caching/database/package-info.java | 4 +
.../com/iluwatar/caching/package-info.java | 24 ++++
5 files changed, 173 insertions(+), 85 deletions(-)
create mode 100644 caching/src/main/java/com/iluwatar/caching/database/package-info.java
create mode 100644 caching/src/main/java/com/iluwatar/caching/package-info.java
diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java
index 14f16f365..0882fc165 100644
--- a/caching/src/main/java/com/iluwatar/caching/App.java
+++ b/caching/src/main/java/com/iluwatar/caching/App.java
@@ -1,26 +1,3 @@
-/*
- * The MIT License
- * Copyright © 2014-2021 Ilkka Seppälä
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
package com.iluwatar.caching;
import com.iluwatar.caching.database.DbManager;
@@ -28,33 +5,42 @@ import com.iluwatar.caching.database.DbManagerFactory;
import lombok.extern.slf4j.Slf4j;
/**
- * The Caching pattern describes how to avoid expensive re-acquisition of resources by not releasing
- * the resources immediately after their use. The resources retain their identity, are kept in some
- * fast-access storage, and are re-used to avoid having to acquire them again. There are four main
- * caching strategies/techniques in this pattern; each with their own pros and cons. They are;
- * write-through
which writes data to the cache and DB in a single transaction,
- * write-around
which writes data immediately into the DB instead of the cache,
- * write-behind
which writes data into the cache initially whilst the data is only
- * written into the DB when the cache is full, and cache-aside
which pushes the
- * responsibility of keeping the data synchronized in both data sources to the application itself.
- * The read-through
strategy is also included in the mentioned four strategies --
- * returns data from the cache to the caller if it exists else queries from DB and
- * stores it into the cache for future use. These strategies determine when the data in the cache
- * should be written back to the backing store (i.e. Database) and help keep both data sources
- * synchronized/up-to-date. This pattern can improve performance and also helps to maintain
- * consistency between data held in the cache and the data in the underlying data store.
+ * The Caching pattern describes how to avoid expensive re-acquisition of
+ * resources by not releasing the resources immediately after their use.
+ * The resources retain their identity, are kept in some fast-access storage,
+ * and are re-used to avoid having to acquire them again. There are four main
+ * caching strategies/techniques in this pattern; each with their own pros and
+ * cons. They are write-through
which writes data to the cache and
+ * DB in a single transaction, write-around
which writes data
+ * immediately into the DB instead of the cache, write-behind
+ * which writes data into the cache initially whilst the data is only
+ * written into the DB when the cache is full, and cache-aside
+ * which pushes the responsibility of keeping the data synchronized in both
+ * data sources to the application itself. The read-through
+ * strategy is also included in the mentioned four strategies --
+ * returns data from the cache to the caller if it exists else
+ * queries from DB and stores it into the cache for future use. These strategies
+ * determine when the data in the cache should be written back to the backing
+ * store (i.e. Database) and help keep both data sources
+ * synchronized/up-to-date. This pattern can improve performance and also helps
+ * to maintainconsistency between data held in the cache and the data in
+ * the underlying data store.
*
- * In this example, the user account ({@link UserAccount}) entity is used as the underlying
- * application data. The cache itself is implemented as an internal (Java) data structure. It adopts
- * a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The four
- * strategies are individually tested. The testing of the cache is restricted towards saving and
- * querying of user accounts from the underlying data store ( {@link DbManager}). The main class (
- * {@link App} is not aware of the underlying mechanics of the application (i.e. save and query) and
- * whether the data is coming from the cache or the DB (i.e. separation of concern). The AppManager
- * ({@link AppManager}) handles the transaction of data to-and-from the underlying data store
- * (depending on the preferred caching policy/strategy).
+ *
In this example, the user account ({@link UserAccount}) entity is used
+ * as the underlying application data. The cache itself is implemented as an
+ * internal (Java) data structure. It adopts a Least-Recently-Used (LRU)
+ * strategy for evicting data from itself when its full. The four
+ * strategies are individually tested. The testing of the cache is restricted
+ * towards saving and querying of user accounts from the
+ * underlying data store( {@link DbManager}). The main class ( {@link App}
+ * is not aware of the underlying mechanics of the application
+ * (i.e. save and query) and whether the data is coming from the cache or the
+ * DB (i.e. separation of concern). The AppManager ({@link AppManager}) handles
+ * the transaction of data to-and-from the underlying data store (depending on
+ * the preferred caching policy/strategy).
*
- * {@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> DBManager}
+ * {@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy -->
+ * DBManager}
*
*
*
@@ -68,10 +54,20 @@ import lombok.extern.slf4j.Slf4j;
*/
@Slf4j
public class App {
+ /**
+ * Constant parameter name to use mongoDB.
+ */
private static final String USE_MONGO_DB = "--mongo";
- private AppManager appManager;
+ /**
+ * Application manager.
+ */
+ private final AppManager appManager;
- public App(boolean isMongo) {
+ /**
+ * Constructor of current App.
+ * @param isMongo boolean
+ */
+ public App(final boolean isMongo) {
DbManager dbManager = DbManagerFactory.initDb(isMongo);
appManager = new AppManager(dbManager);
appManager.initDb();
@@ -82,20 +78,20 @@ public class App {
*
* @param args command line args
*/
- public static void main(String[] args) {
+ public static void main(final String[] args) {
// VirtualDB (instead of MongoDB) was used in running the JUnit tests
// and the App class to avoid Maven compilation errors. Set flag to
// true to run the tests with MongoDB (provided that MongoDB is
// installed and socket connection is open).
App app = new App(isDbMongo(args));
app.useReadAndWriteThroughStrategy();
- System.out.println("=====================================================");
+ System.out.println("==============================================");
app.useReadThroughAndWriteAroundStrategy();
- System.out.println("=====================================================");
+ System.out.println("==============================================");
app.useReadThroughAndWriteBehindStrategy();
- System.out.println("=====================================================");
+ System.out.println("==============================================");
app.useCacheAsideStategy();
- System.out.println("=====================================================");
+ System.out.println("==============================================");
}
/**
@@ -103,7 +99,7 @@ public class App {
* @param args input params
* @return true if there is "--mongo" parameter in arguments
*/
- private static boolean isDbMongo(String[] args) {
+ private static boolean isDbMongo(final String[] args) {
for (String arg : args) {
if (arg.equals(USE_MONGO_DB)) {
return true;
@@ -156,9 +152,15 @@ public class App {
LOGGER.info("# CachingPolicy.BEHIND");
appManager.initCachingPolicy(CachingPolicy.BEHIND);
- var userAccount3 = new UserAccount("003", "Adam", "He likes food.");
- var userAccount4 = new UserAccount("004", "Rita", "She hates cats.");
- var userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard.");
+ var userAccount3 = new UserAccount("003",
+ "Adam",
+ "He likes food.");
+ var userAccount4 = new UserAccount("004",
+ "Rita",
+ "She hates cats.");
+ var userAccount5 = new UserAccount("005",
+ "Isaac",
+ "He is allergic to mustard.");
appManager.save(userAccount3);
appManager.save(userAccount4);
@@ -166,7 +168,9 @@ public class App {
LOGGER.info(appManager.printCacheContent());
appManager.find("003");
LOGGER.info(appManager.printCacheContent());
- UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child.");
+ UserAccount userAccount6 = new UserAccount("006",
+ "Yasha",
+ "She is an only child.");
appManager.save(userAccount6);
LOGGER.info(appManager.printCacheContent());
appManager.find("004");
@@ -181,9 +185,15 @@ public class App {
appManager.initCachingPolicy(CachingPolicy.ASIDE);
LOGGER.info(appManager.printCacheContent());
- var userAccount3 = new UserAccount("003", "Adam", "He likes food.");
- var userAccount4 = new UserAccount("004", "Rita", "She hates cats.");
- var userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard.");
+ var userAccount3 = new UserAccount("003",
+ "Adam",
+ "He likes food.");
+ var userAccount4 = new UserAccount("004",
+ "Rita",
+ "She hates cats.");
+ var userAccount5 = new UserAccount("005",
+ "Isaac",
+ "He is allergic to mustard.");
appManager.save(userAccount3);
appManager.save(userAccount4);
appManager.save(userAccount5);
diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java
index 880791034..cdf26b194 100644
--- a/caching/src/main/java/com/iluwatar/caching/AppManager.java
+++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java
@@ -29,28 +29,41 @@ import com.iluwatar.caching.database.DbManager;
import lombok.extern.slf4j.Slf4j;
/**
- * AppManager helps to bridge the gap in communication between the main class and the application's
- * back-end. DB connection is initialized through this class. The chosen caching strategy/policy is
- * also initialized here. Before the cache can be used, the size of the cache has to be set.
- * Depending on the chosen caching policy, AppManager will call the appropriate function in the
- * CacheStore class.
+ * AppManager helps to bridge the gap in communication between the main class
+ * and the application's back-end. DB connection is initialized through this
+ * class. The chosen caching strategy/policy is also initialized here.
+ * Before the cache can be used, the size of the cache has to be set.
+ * Depending on the chosen caching policy, AppManager will call the
+ * appropriate function in the CacheStore class.
*/
@Slf4j
public class AppManager {
-
+ /**
+ * Caching Policy.
+ */
private static CachingPolicy cachingPolicy;
+ /**
+ * Database Manager.
+ */
private final DbManager dbManager;
+ /**
+ * Cache Store.
+ */
private final CacheStore cacheStore;
- public AppManager(DbManager dbManager) {
- this.dbManager = dbManager;
- this.cacheStore = new CacheStore(dbManager);
+ /**
+ * Constructor.
+ * @param newDbManager database manager
+ */
+ public AppManager(final DbManager newDbManager) {
+ this.dbManager = newDbManager;
+ this.cacheStore = new CacheStore(newDbManager);
}
/**
- * Developer/Tester is able to choose whether the application should use MongoDB as its underlying
- * data storage or a simple Java data structure to (temporarily) store the data/objects during
- * runtime.
+ * Developer/Tester is able to choose whether the application should use
+ * MongoDB as its underlying data storage or a simple Java data structure
+ * to (temporarily) store the data/objects during runtime.
*/
public void initDb() {
dbManager.connect();
@@ -58,8 +71,9 @@ public class AppManager {
/**
* Initialize caching policy.
+ * @param policy is a {@link CachingPolicy}
*/
- public void initCachingPolicy(CachingPolicy policy) {
+ public void initCachingPolicy(final CachingPolicy policy) {
cachingPolicy = policy;
if (cachingPolicy == CachingPolicy.BEHIND) {
Runtime.getRuntime().addShutdownHook(new Thread(cacheStore::flushCache));
@@ -69,10 +83,13 @@ public class AppManager {
/**
* Find user account.
+ * @param userId String
+ * @return {@link UserAccount}
*/
- public UserAccount find(String userId) {
+ public UserAccount find(final String userId) {
LOGGER.info("Trying to find {} in cache", userId);
- if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) {
+ if (cachingPolicy == CachingPolicy.THROUGH
+ || cachingPolicy == CachingPolicy.AROUND) {
return cacheStore.readThrough(userId);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
return cacheStore.readThroughWithWriteBackPolicy(userId);
@@ -84,8 +101,9 @@ public class AppManager {
/**
* Save user account.
+ * @param userAccount {@link UserAccount}
*/
- public void save(UserAccount userAccount) {
+ public void save(final UserAccount userAccount) {
LOGGER.info("Save record!");
if (cachingPolicy == CachingPolicy.THROUGH) {
cacheStore.writeThrough(userAccount);
@@ -98,25 +116,33 @@ public class AppManager {
}
}
+ /**
+ * Returns String.
+ * @return String
+ */
public String printCacheContent() {
return cacheStore.print();
}
/**
* Cache-Aside save user account helper.
+ * @param userAccount {@link UserAccount}
*/
- private void saveAside(UserAccount userAccount) {
+ private void saveAside(final UserAccount userAccount) {
dbManager.updateDb(userAccount);
cacheStore.invalidate(userAccount.getUserId());
}
/**
* Cache-Aside find user account helper.
+ * @param userId String
+ * @return {@link UserAccount}
*/
- private UserAccount findAside(String userId) {
+ private UserAccount findAside(final String userId) {
return Optional.ofNullable(cacheStore.get(userId))
.or(() -> {
- Optional userAccount = Optional.ofNullable(dbManager.readFromDb(userId));
+ Optional userAccount =
+ Optional.ofNullable(dbManager.readFromDb(userId));
userAccount.ifPresent(account -> cacheStore.set(userId, account));
return userAccount;
})
diff --git a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java
index d1ec2dd3f..496384abc 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java
@@ -3,14 +3,38 @@ package com.iluwatar.caching.database;
import com.iluwatar.caching.UserAccount;
/**
- * DBManager handles the communication with the underlying data store i.e. Database. It contains
- * the implemented methods for querying, inserting, and updating data. MongoDB was used as the
- * database for the application.
+ * DBManager handles the communication with the underlying data store i.e.
+ * Database. It contains the implemented methods for querying, inserting,
+ * and updating data. MongoDB was used as the database for the application.
*/
public interface DbManager {
+ /**
+ * Connect to DB.
+ */
void connect();
+
+ /**
+ * Read from DB.
+ * @param userId {@link String}
+ * @return {@link UserAccount}
+ */
UserAccount readFromDb(String userId);
+ /**
+ * Write to DB.
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
UserAccount writeToDb(UserAccount userAccount);
+ /**
+ * Update record.
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
UserAccount updateDb(UserAccount userAccount);
+ /**
+ * Update record or Insert if not exists.
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
UserAccount upsertDb(UserAccount userAccount);
}
diff --git a/caching/src/main/java/com/iluwatar/caching/database/package-info.java b/caching/src/main/java/com/iluwatar/caching/database/package-info.java
new file mode 100644
index 000000000..f81386688
--- /dev/null
+++ b/caching/src/main/java/com/iluwatar/caching/database/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Database classes
+ */
+package com.iluwatar.caching.database;
diff --git a/caching/src/main/java/com/iluwatar/caching/package-info.java b/caching/src/main/java/com/iluwatar/caching/package-info.java
new file mode 100644
index 000000000..d670c1bd0
--- /dev/null
+++ b/caching/src/main/java/com/iluwatar/caching/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2021 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.caching;
\ No newline at end of file
From c150e5f38b23fa1a63436f59542b6aa6e09d45d0 Mon Sep 17 00:00:00 2001
From: Victor Z
Date: Sun, 22 Aug 2021 11:12:53 +0300
Subject: [PATCH 04/25] Fix CacheStore errors from checkstyle plugin 107 left
---
.../java/com/iluwatar/caching/CacheStore.java | 52 ++++++++++++++-----
1 file changed, 40 insertions(+), 12 deletions(-)
diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java
index 3e229d580..35e56381f 100644
--- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java
+++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java
@@ -35,20 +35,34 @@ import lombok.extern.slf4j.Slf4j;
*/
@Slf4j
public class CacheStore {
+ /**
+ * Cach capacity.
+ */
private static final int CAPACITY = 3;
+ /**
+ * Lru cache see {@link LruCache}.
+ */
private static LruCache cache;
+ /**
+ * DbManager.
+ */
private DbManager dbManager;
- public CacheStore(DbManager dbManager) {
- this.dbManager = dbManager;
+ /**
+ * Cache Store.
+ * @param dataBaseManager {@link DbManager}
+ */
+ public CacheStore(final DbManager dataBaseManager) {
+ this.dbManager = dataBaseManager;
initCapacity(CAPACITY);
}
/**
* Init cache capacity.
+ * @param capacity int
*/
- public void initCapacity(int capacity) {
+ public void initCapacity(final int capacity) {
if (cache == null) {
cache = new LruCache(capacity);
} else {
@@ -58,8 +72,10 @@ public class CacheStore {
/**
* Get user account using read-through cache.
+ * @param userId {@link String}
+ * @return {@link UserAccount}
*/
- public UserAccount readThrough(String userId) {
+ public UserAccount readThrough(final String userId) {
if (cache.contains(userId)) {
LOGGER.info("# Found in Cache!");
return cache.get(userId);
@@ -72,8 +88,9 @@ public class CacheStore {
/**
* Get user account using write-through cache.
+ * @param userAccount {@link UserAccount}
*/
- public void writeThrough(UserAccount userAccount) {
+ public void writeThrough(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
dbManager.updateDb(userAccount);
} else {
@@ -84,11 +101,13 @@ public class CacheStore {
/**
* Get user account using write-around cache.
+ * @param userAccount {@link UserAccount}
*/
- public void writeAround(UserAccount userAccount) {
+ public void writeAround(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
dbManager.updateDb(userAccount);
- cache.invalidate(userAccount.getUserId()); // Cache data has been updated -- remove older
+ // Cache data has been updated -- remove older
+ cache.invalidate(userAccount.getUserId());
// version from cache.
} else {
dbManager.writeToDb(userAccount);
@@ -97,8 +116,10 @@ public class CacheStore {
/**
* Get user account using read-through cache with write-back policy.
+ * @param userId {@link String}
+ * @return {@link UserAccount}
*/
- public UserAccount readThroughWithWriteBackPolicy(String userId) {
+ public UserAccount readThroughWithWriteBackPolicy(final String userId) {
if (cache.contains(userId)) {
LOGGER.info("# Found in cache!");
return cache.get(userId);
@@ -116,8 +137,9 @@ public class CacheStore {
/**
* Set user account.
+ * @param userAccount {@link UserAccount}
*/
- public void writeBehind(UserAccount userAccount) {
+ public void writeBehind(final UserAccount userAccount) {
if (cache.isFull() && !cache.contains(userAccount.getUserId())) {
LOGGER.info("# Cache is FULL! Writing LRU data to DB...");
UserAccount toBeWrittenToDb = cache.getLruData();
@@ -148,6 +170,7 @@ public class CacheStore {
/**
* Print user accounts.
+ * @return {@link String}
*/
public String print() {
return Optional.ofNullable(cache)
@@ -160,22 +183,27 @@ public class CacheStore {
/**
* Delegate to backing cache store.
+ * @param userId {@link String}
+ * @return {@link UserAccount}
*/
- public UserAccount get(String userId) {
+ public UserAccount get(final String userId) {
return cache.get(userId);
}
/**
* Delegate to backing cache store.
+ * @param userId {@link String}
+ * @param userAccount {@link UserAccount}
*/
- public void set(String userId, UserAccount userAccount) {
+ public void set(final String userId, final UserAccount userAccount) {
cache.set(userId, userAccount);
}
/**
* Delegate to backing cache store.
+ * @param userId {@link String}
*/
- public void invalidate(String userId) {
+ public void invalidate(final String userId) {
cache.invalidate(userId);
}
}
From aff7ef8782e8431eb30e2c906d62642dc4746380 Mon Sep 17 00:00:00 2001
From: Victor Zalevskii
Date: Mon, 23 Aug 2021 12:37:24 +0300
Subject: [PATCH 05/25] Fix last errors in checkstyle.
---
.../com/iluwatar/caching/CachingPolicy.java | 15 +++
.../java/com/iluwatar/caching/LruCache.java | 123 +++++++++++++-----
.../com/iluwatar/caching/UserAccount.java | 9 ++
.../caching/constants/CachingConstants.java | 20 ++-
.../caching/constants/package-info.java | 4 +
.../caching/database/DbManagerFactory.java | 21 ++-
.../iluwatar/caching/database/MongoDb.java | 77 ++++++++---
.../iluwatar/caching/database/VirtualDb.java | 36 ++++-
.../caching/database/package-info.java | 2 +-
.../com/iluwatar/caching/package-info.java | 5 +-
10 files changed, 248 insertions(+), 64 deletions(-)
create mode 100644 caching/src/main/java/com/iluwatar/caching/constants/package-info.java
diff --git a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java
index 0a42cf812..d24ad0173 100644
--- a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java
+++ b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java
@@ -32,10 +32,25 @@ import lombok.Getter;
@AllArgsConstructor
@Getter
public enum CachingPolicy {
+ /**
+ * Through.
+ */
THROUGH("through"),
+ /**
+ * AROUND.
+ */
AROUND("around"),
+ /**
+ * BEHIND.
+ */
BEHIND("behind"),
+ /**
+ * ASIDE.
+ */
ASIDE("aside");
+ /**
+ * Policy value.
+ */
private final String policy;
}
diff --git a/caching/src/main/java/com/iluwatar/caching/LruCache.java b/caching/src/main/java/com/iluwatar/caching/LruCache.java
index a320e35dc..f23eb79b4 100644
--- a/caching/src/main/java/com/iluwatar/caching/LruCache.java
+++ b/caching/src/main/java/com/iluwatar/caching/LruCache.java
@@ -30,40 +30,78 @@ import java.util.Map;
import lombok.extern.slf4j.Slf4j;
/**
- * Data structure/implementation of the application's cache. The data structure consists of a hash
- * table attached with a doubly linked-list. The linked-list helps in capturing and maintaining the
- * LRU data in the cache. When a data is queried (from the cache), added (to the cache), or updated,
- * the data is moved to the front of the list to depict itself as the most-recently-used data. The
- * LRU data is always at the end of the list.
+ * Data structure/implementation of the application's cache. The data structure
+ * consists of a hash table attached with a doubly linked-list. The linked-list
+ * helps in capturing and maintaining the LRU data in the cache. When a data is
+ * queried (from the cache), added (to the cache), or updated, the data is
+ * moved to the front of the list to depict itself as the most-recently-used
+ * data. The LRU data is always at the end of the list.
*/
@Slf4j
public class LruCache {
-
+ /**
+ * Static class Node.
+ */
static class Node {
- String userId;
- UserAccount userAccount;
- Node previous;
- Node next;
+ /**
+ * user id.
+ */
+ private final String userId;
+ /**
+ * User Account.
+ */
+ private UserAccount userAccount;
+ /**
+ * previous.
+ */
+ private Node previous;
+ /**
+ * next.
+ */
+ private Node next;
- public Node(String userId, UserAccount userAccount) {
- this.userId = userId;
- this.userAccount = userAccount;
+ /**
+ * Node definition.
+ * @param id String
+ * @param account {@link UserAccount}
+ */
+ Node(final String id, final UserAccount account) {
+ this.userId = id;
+ this.userAccount = account;
}
}
- int capacity;
- Map cache = new HashMap<>();
- Node head;
- Node end;
+ /**
+ * Capacity of Cache.
+ */
+ private int capacity;
+ /**
+ * Cache {@link HashMap}.
+ */
+ private Map cache = new HashMap<>();
+ /**
+ * Head.
+ */
+ private Node head;
+ /**
+ * End.
+ */
+ private Node end;
- public LruCache(int capacity) {
- this.capacity = capacity;
+ /**
+ * Constructor.
+ * @param cap Integer.
+ */
+ public LruCache(final int cap) {
+ this.capacity = cap;
}
/**
* Get user account.
+ * @param userId String
+ * @return {@link UserAccount}
*/
- public UserAccount get(String userId) {
+ public UserAccount get(final String userId) {
if (cache.containsKey(userId)) {
var node = cache.get(userId);
remove(node);
@@ -75,8 +113,9 @@ public class LruCache {
/**
* Remove node from linked list.
+ * @param node {@link Node}
*/
- public void remove(Node node) {
+ public void remove(final Node node) {
if (node.previous != null) {
node.previous.next = node.next;
} else {
@@ -91,8 +130,9 @@ public class LruCache {
/**
* Move node to the front of the list.
+ * @param node {@link Node}
*/
- public void setHead(Node node) {
+ public void setHead(final Node node) {
node.next = head;
node.previous = null;
if (head != null) {
@@ -106,8 +146,10 @@ public class LruCache {
/**
* Set user account.
+ * @param userAccount {@link UserAccount}
+ * @param userId {@link String}
*/
- public void set(String userId, UserAccount userAccount) {
+ public void set(final String userId, final UserAccount userAccount) {
if (cache.containsKey(userId)) {
var old = cache.get(userId);
old.userAccount = userAccount;
@@ -127,25 +169,40 @@ public class LruCache {
}
}
- public boolean contains(String userId) {
+ /**
+ * Che if Cache cintains the userId.
+ * @param userId {@link String}
+ * @return boolean
+ */
+ public boolean contains(final String userId) {
return cache.containsKey(userId);
}
- /**
- * Invalidate cache for user.
- */
- public void invalidate(String userId) {
+ /**
+ * Invalidate cache for user.
+ * @param userId {@link String}
+ */
+ public void invalidate(final String userId) {
var toBeRemoved = cache.remove(userId);
if (toBeRemoved != null) {
- LOGGER.info("# {} has been updated! Removing older version from cache...", userId);
+ LOGGER.info("# {} has been updated! "
+ + "Removing older version from cache...", userId);
remove(toBeRemoved);
}
}
+ /**
+ * Is cache full?
+ * @return boolean
+ */
public boolean isFull() {
return cache.size() >= capacity;
}
+ /**
+ * Get LRU data.
+ * @return {@link UserAccount}
+ */
public UserAccount getLruData() {
return end.userAccount;
}
@@ -161,6 +218,7 @@ public class LruCache {
/**
* Returns cache data in list form.
+ * @return {@link List}
*/
public List getCacheDataInListForm() {
var listOfCacheData = new ArrayList();
@@ -174,10 +232,13 @@ public class LruCache {
/**
* Set cache capacity.
+ * @param newCapacity int
*/
- public void setCapacity(int newCapacity) {
+ public void setCapacity(final int newCapacity) {
if (capacity > newCapacity) {
- clear(); // Behavior can be modified to accommodate for decrease in cache size. For now, we'll
+ // Behavior can be modified to accommodate
+ // for decrease in cache size. For now, we'll
+ clear();
// just clear the cache.
} else {
this.capacity = newCapacity;
diff --git a/caching/src/main/java/com/iluwatar/caching/UserAccount.java b/caching/src/main/java/com/iluwatar/caching/UserAccount.java
index 3681a931b..800f14c9e 100644
--- a/caching/src/main/java/com/iluwatar/caching/UserAccount.java
+++ b/caching/src/main/java/com/iluwatar/caching/UserAccount.java
@@ -36,7 +36,16 @@ import lombok.ToString;
@AllArgsConstructor
@ToString
public class UserAccount {
+ /**
+ * User Id.
+ */
private String userId;
+ /**
+ * User Name.
+ */
private String userName;
+ /**
+ * Additional Info.
+ */
private String additionalInfo;
}
diff --git a/caching/src/main/java/com/iluwatar/caching/constants/CachingConstants.java b/caching/src/main/java/com/iluwatar/caching/constants/CachingConstants.java
index 797a561fd..69cd63fef 100644
--- a/caching/src/main/java/com/iluwatar/caching/constants/CachingConstants.java
+++ b/caching/src/main/java/com/iluwatar/caching/constants/CachingConstants.java
@@ -26,11 +26,27 @@ package com.iluwatar.caching.constants;
/**
* Constant class for defining constants.
*/
-public class CachingConstants {
-
+public final class CachingConstants {
+ /**
+ * User Account.
+ */
public static final String USER_ACCOUNT = "user_accounts";
+ /**
+ * User ID.
+ */
public static final String USER_ID = "userID";
+ /**
+ * User Name.
+ */
public static final String USER_NAME = "userName";
+ /**
+ * Additional Info.
+ */
public static final String ADD_INFO = "additionalInfo";
+ /**
+ * Constructor.
+ */
+ private CachingConstants() {
+ }
}
diff --git a/caching/src/main/java/com/iluwatar/caching/constants/package-info.java b/caching/src/main/java/com/iluwatar/caching/constants/package-info.java
new file mode 100644
index 000000000..8ae963658
--- /dev/null
+++ b/caching/src/main/java/com/iluwatar/caching/constants/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Constants.
+ */
+package com.iluwatar.caching.constants;
diff --git a/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java b/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java
index 0e6ed9fe1..f68177203 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java
@@ -1,8 +1,23 @@
package com.iluwatar.caching.database;
-public class DbManagerFactory {
- public static DbManager initDb(boolean isMongo){
- if(isMongo){
+/**
+ * Creates the database connection accroding the input parameter.
+ */
+public final class DbManagerFactory {
+ /**
+ * Private constructor.
+ */
+ private DbManagerFactory() {
+ }
+
+ /**
+ * Init database.
+ *
+ * @param isMongo boolean
+ * @return {@link DbManager}
+ */
+ public static DbManager initDb(final boolean isMongo) {
+ if (isMongo) {
return new MongoDb();
}
return new VirtualDb();
diff --git a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
index 50a5d805d..81395223c 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
@@ -7,72 +7,111 @@ import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.UpdateOptions;
import org.bson.Document;
+import static com.iluwatar.caching.constants.CachingConstants.USER_NAME;
+import static com.iluwatar.caching.constants.CachingConstants.ADD_INFO;
+import static com.iluwatar.caching.constants.CachingConstants.USER_ID;
+import static com.iluwatar.caching.constants.CachingConstants.USER_ACCOUNT;
+
/**
* Implementation of DatabaseManager.
* implements base methods to work with MongoDb.
*/
public class MongoDb implements DbManager {
+ /**
+ * Mongo db.
+ */
private MongoDatabase db;
+ /**
+ * Connect to Db.
+ */
@Override
public void connect() {
MongoClient mongoClient = new MongoClient();
db = mongoClient.getDatabase("test");
}
+ /**
+ * Read data from DB.
+ *
+ * @param userId {@link String}
+ * @return {@link UserAccount}
+ */
@Override
- public UserAccount readFromDb(String userId) {
+ public UserAccount readFromDb(final String userId) {
if (db == null) {
connect();
}
var iterable = db
.getCollection(CachingConstants.USER_ACCOUNT)
- .find(new Document(CachingConstants.USER_ID, userId));
+ .find(new Document(USER_ID, userId));
if (iterable == null) {
return null;
}
Document doc = iterable.first();
- String userName = doc.getString(CachingConstants.USER_NAME);
- String appInfo = doc.getString(CachingConstants.ADD_INFO);
+ String userName = doc.getString(USER_NAME);
+ String appInfo = doc.getString(ADD_INFO);
return new UserAccount(userId, userName, appInfo);
}
+ /**
+ * Write data to DB.
+ *
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
@Override
- public UserAccount writeToDb(UserAccount userAccount) {
+ public UserAccount writeToDb(final UserAccount userAccount) {
if (db == null) {
connect();
}
- db.getCollection(CachingConstants.USER_ACCOUNT).insertOne(
- new Document(CachingConstants.USER_ID, userAccount.getUserId())
- .append(CachingConstants.USER_NAME, userAccount.getUserName())
- .append(CachingConstants.ADD_INFO, userAccount.getAdditionalInfo())
+ db.getCollection(USER_ACCOUNT).insertOne(
+ new Document(USER_ID, userAccount.getUserId())
+ .append(USER_NAME, userAccount.getUserName())
+ .append(ADD_INFO, userAccount.getAdditionalInfo())
);
return userAccount;
}
+ /**
+ * Update DB.
+ *
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
@Override
- public UserAccount updateDb(UserAccount userAccount) {
+ public UserAccount updateDb(final UserAccount userAccount) {
if (db == null) {
connect();
}
- db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
- new Document(CachingConstants.USER_ID, userAccount.getUserId()),
- new Document("$set", new Document(CachingConstants.USER_NAME, userAccount.getUserName())
- .append(CachingConstants.ADD_INFO, userAccount.getAdditionalInfo())));
+ Document id = new Document(USER_ID, userAccount.getUserId());
+ Document dataSet = new Document(USER_NAME, userAccount.getUserName())
+ .append(ADD_INFO, userAccount.getAdditionalInfo());
+ db.getCollection(CachingConstants.USER_ACCOUNT)
+ .updateOne(id, new Document("$set", dataSet));
return userAccount;
}
+ /**
+ * Update data if exists.
+ *
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
@Override
- public UserAccount upsertDb(UserAccount userAccount) {
+ public UserAccount upsertDb(final UserAccount userAccount) {
if (db == null) {
connect();
}
+ String userId = userAccount.getUserId();
+ String userName = userAccount.getUserName();
+ String additionalInfo = userAccount.getAdditionalInfo();
db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
- new Document(CachingConstants.USER_ID, userAccount.getUserId()),
+ new Document(USER_ID, userId),
new Document("$set",
- new Document(CachingConstants.USER_ID, userAccount.getUserId())
- .append(CachingConstants.USER_NAME, userAccount.getUserName())
- .append(CachingConstants.ADD_INFO, userAccount.getAdditionalInfo())
+ new Document(USER_ID, userId)
+ .append(USER_NAME, userName)
+ .append(ADD_INFO, additionalInfo)
),
new UpdateOptions().upsert(true)
);
diff --git a/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java
index af67225ad..22e091b83 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java
@@ -10,35 +10,61 @@ import java.util.Map;
* implements base methods to work with hashMap as database.
*/
public class VirtualDb implements DbManager {
+ /**
+ * Virtual DataBase.
+ */
private Map virtualDB;
+ /**
+ * Creates new HashMap.
+ */
@Override
public void connect() {
virtualDB = new HashMap<>();
}
+ /**
+ * Read from Db.
+ * @param userId {@link String}
+ * @return {@link UserAccount}
+ */
@Override
- public UserAccount readFromDb(String userId) {
+ public UserAccount readFromDb(final String userId) {
if (virtualDB.containsKey(userId)) {
return virtualDB.get(userId);
}
return null;
}
+ /**
+ * Write to DB.
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
@Override
- public UserAccount writeToDb(UserAccount userAccount) {
+ public UserAccount writeToDb(final UserAccount userAccount) {
virtualDB.put(userAccount.getUserId(), userAccount);
return userAccount;
}
+ /**
+ * Update reecord in DB.
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
@Override
- public UserAccount updateDb(UserAccount userAccount) {
+ public UserAccount updateDb(final UserAccount userAccount) {
virtualDB.put(userAccount.getUserId(), userAccount);
return userAccount;
}
+ /**
+ * Update.
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
@Override
- public UserAccount upsertDb(UserAccount userAccount) {
+ public UserAccount upsertDb(final UserAccount userAccount) {
return updateDb(userAccount);
}
-}
\ No newline at end of file
+}
diff --git a/caching/src/main/java/com/iluwatar/caching/database/package-info.java b/caching/src/main/java/com/iluwatar/caching/database/package-info.java
index f81386688..56deee71d 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/package-info.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/package-info.java
@@ -1,4 +1,4 @@
/**
- * Database classes
+ * Database classes.
*/
package com.iluwatar.caching.database;
diff --git a/caching/src/main/java/com/iluwatar/caching/package-info.java b/caching/src/main/java/com/iluwatar/caching/package-info.java
index d670c1bd0..fed1ab2a8 100644
--- a/caching/src/main/java/com/iluwatar/caching/package-info.java
+++ b/caching/src/main/java/com/iluwatar/caching/package-info.java
@@ -1,4 +1,4 @@
-/*
+/**
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
*
@@ -20,5 +20,4 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-
-package com.iluwatar.caching;
\ No newline at end of file
+package com.iluwatar.caching;
From dabe4d2022fddd6b8f211a818e9e9a11dada44c6 Mon Sep 17 00:00:00 2001
From: Victor Zalevskii
Date: Fri, 27 Aug 2021 13:27:50 +0300
Subject: [PATCH 06/25] Fix sonar issues
---
caching/src/main/java/com/iluwatar/caching/App.java | 9 +++++----
.../main/java/com/iluwatar/caching/AppManager.java | 4 +++-
.../main/java/com/iluwatar/caching/CacheStore.java | 4 ++--
.../java/com/iluwatar/caching/database/MongoDb.java | 2 +-
.../com/iluwatar/caching/database/VirtualDb.java | 13 ++++++-------
5 files changed, 17 insertions(+), 15 deletions(-)
diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java
index 0882fc165..631d23a2a 100644
--- a/caching/src/main/java/com/iluwatar/caching/App.java
+++ b/caching/src/main/java/com/iluwatar/caching/App.java
@@ -85,13 +85,14 @@ public class App {
// installed and socket connection is open).
App app = new App(isDbMongo(args));
app.useReadAndWriteThroughStrategy();
- System.out.println("==============================================");
+ String splitLine = "==============================================";
+ LOGGER.info(splitLine);
app.useReadThroughAndWriteAroundStrategy();
- System.out.println("==============================================");
+ LOGGER.info(splitLine);
app.useReadThroughAndWriteBehindStrategy();
- System.out.println("==============================================");
+ LOGGER.info(splitLine);
app.useCacheAsideStategy();
- System.out.println("==============================================");
+ LOGGER.info(splitLine);
}
/**
diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java
index cdf26b194..ac3100523 100644
--- a/caching/src/main/java/com/iluwatar/caching/AppManager.java
+++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java
@@ -26,6 +26,7 @@ package com.iluwatar.caching;
import java.util.Optional;
import com.iluwatar.caching.database.DbManager;
+import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
@@ -37,11 +38,12 @@ import lombok.extern.slf4j.Slf4j;
* appropriate function in the CacheStore class.
*/
@Slf4j
+@Data
public class AppManager {
/**
* Caching Policy.
*/
- private static CachingPolicy cachingPolicy;
+ private CachingPolicy cachingPolicy;
/**
* Database Manager.
*/
diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java
index 35e56381f..ca67fd334 100644
--- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java
+++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java
@@ -43,11 +43,11 @@ public class CacheStore {
/**
* Lru cache see {@link LruCache}.
*/
- private static LruCache cache;
+ private LruCache cache;
/**
* DbManager.
*/
- private DbManager dbManager;
+ private final DbManager dbManager;
/**
* Cache Store.
diff --git a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
index 81395223c..fceedc9aa 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
@@ -45,7 +45,7 @@ public class MongoDb implements DbManager {
var iterable = db
.getCollection(CachingConstants.USER_ACCOUNT)
.find(new Document(USER_ID, userId));
- if (iterable == null) {
+ if (iterable.first()==null) {
return null;
}
Document doc = iterable.first();
diff --git a/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java
index 22e091b83..bd1601ee7 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java
@@ -13,14 +13,14 @@ public class VirtualDb implements DbManager {
/**
* Virtual DataBase.
*/
- private Map virtualDB;
+ private Map db;
/**
* Creates new HashMap.
*/
@Override
public void connect() {
- virtualDB = new HashMap<>();
+ db = new HashMap<>();
}
/**
@@ -30,8 +30,8 @@ public class VirtualDb implements DbManager {
*/
@Override
public UserAccount readFromDb(final String userId) {
- if (virtualDB.containsKey(userId)) {
- return virtualDB.get(userId);
+ if (db.containsKey(userId)) {
+ return db.get(userId);
}
return null;
}
@@ -43,7 +43,7 @@ public class VirtualDb implements DbManager {
*/
@Override
public UserAccount writeToDb(final UserAccount userAccount) {
- virtualDB.put(userAccount.getUserId(), userAccount);
+ db.put(userAccount.getUserId(), userAccount);
return userAccount;
}
@@ -54,8 +54,7 @@ public class VirtualDb implements DbManager {
*/
@Override
public UserAccount updateDb(final UserAccount userAccount) {
- virtualDB.put(userAccount.getUserId(), userAccount);
- return userAccount;
+ return writeToDb(userAccount);
}
/**
From 49039843e5921bc61ce3e00bdb75465c91df13b8 Mon Sep 17 00:00:00 2001
From: Victor Zalevskii
Date: Fri, 27 Aug 2021 15:29:58 +0300
Subject: [PATCH 07/25] Fix issues in VALIDATE phase
---
.../main/java/com/iluwatar/caching/App.java | 274 +++++++++---------
.../java/com/iluwatar/caching/AppManager.java | 4 +-
.../java/com/iluwatar/caching/CacheStore.java | 2 +-
.../java/com/iluwatar/caching/LruCache.java | 50 ++--
.../iluwatar/caching/database/DbManager.java | 59 ++--
.../caching/database/DbManagerFactory.java | 32 +-
.../iluwatar/caching/database/MongoDb.java | 192 ++++++------
.../iluwatar/caching/database/VirtualDb.java | 104 +++----
.../com/iluwatar/caching/package-info.java | 3 -
9 files changed, 368 insertions(+), 352 deletions(-)
diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java
index 631d23a2a..2c7cbebf7 100644
--- a/caching/src/main/java/com/iluwatar/caching/App.java
+++ b/caching/src/main/java/com/iluwatar/caching/App.java
@@ -54,155 +54,155 @@ import lombok.extern.slf4j.Slf4j;
*/
@Slf4j
public class App {
- /**
- * Constant parameter name to use mongoDB.
- */
- private static final String USE_MONGO_DB = "--mongo";
- /**
- * Application manager.
- */
- private final AppManager appManager;
+ /**
+ * Constant parameter name to use mongoDB.
+ */
+ private static final String USE_MONGO_DB = "--mongo";
+ /**
+ * Application manager.
+ */
+ private final AppManager appManager;
- /**
- * Constructor of current App.
- * @param isMongo boolean
- */
- public App(final boolean isMongo) {
- DbManager dbManager = DbManagerFactory.initDb(isMongo);
- appManager = new AppManager(dbManager);
- appManager.initDb();
+ /**
+ * Constructor of current App.
+ * @param isMongo boolean
+ */
+ public App(final boolean isMongo) {
+ DbManager dbManager = DbManagerFactory.initDb(isMongo);
+ appManager = new AppManager(dbManager);
+ appManager.initDb();
+ }
+
+ /**
+ * Program entry point.
+ *
+ * @param args command line args
+ */
+ public static void main(final String[] args) {
+ // VirtualDB (instead of MongoDB) was used in running the JUnit tests
+ // and the App class to avoid Maven compilation errors. Set flag to
+ // true to run the tests with MongoDB (provided that MongoDB is
+ // installed and socket connection is open).
+ App app = new App(isDbMongo(args));
+ app.useReadAndWriteThroughStrategy();
+ String splitLine = "==============================================";
+ LOGGER.info(splitLine);
+ app.useReadThroughAndWriteAroundStrategy();
+ LOGGER.info(splitLine);
+ app.useReadThroughAndWriteBehindStrategy();
+ LOGGER.info(splitLine);
+ app.useCacheAsideStategy();
+ LOGGER.info(splitLine);
+ }
+
+ /**
+ * Check the input parameters. if
+ * @param args input params
+ * @return true if there is "--mongo" parameter in arguments
+ */
+ private static boolean isDbMongo(final String[] args) {
+ for (String arg : args) {
+ if (arg.equals(USE_MONGO_DB)) {
+ return true;
+ }
}
+ return false;
+ }
- /**
- * Program entry point.
- *
- * @param args command line args
- */
- public static void main(final String[] args) {
- // VirtualDB (instead of MongoDB) was used in running the JUnit tests
- // and the App class to avoid Maven compilation errors. Set flag to
- // true to run the tests with MongoDB (provided that MongoDB is
- // installed and socket connection is open).
- App app = new App(isDbMongo(args));
- app.useReadAndWriteThroughStrategy();
- String splitLine = "==============================================";
- LOGGER.info(splitLine);
- app.useReadThroughAndWriteAroundStrategy();
- LOGGER.info(splitLine);
- app.useReadThroughAndWriteBehindStrategy();
- LOGGER.info(splitLine);
- app.useCacheAsideStategy();
- LOGGER.info(splitLine);
- }
+ /**
+ * Read-through and write-through.
+ */
+ public void useReadAndWriteThroughStrategy() {
+ LOGGER.info("# CachingPolicy.THROUGH");
+ appManager.initCachingPolicy(CachingPolicy.THROUGH);
- /**
- * Check the input parameters. if
- * @param args input params
- * @return true if there is "--mongo" parameter in arguments
- */
- private static boolean isDbMongo(final String[] args) {
- for (String arg : args) {
- if (arg.equals(USE_MONGO_DB)) {
- return true;
- }
- }
- return false;
- }
+ var userAccount1 = new UserAccount("001", "John", "He is a boy.");
- /**
- * Read-through and write-through.
- */
- public void useReadAndWriteThroughStrategy() {
- LOGGER.info("# CachingPolicy.THROUGH");
- appManager.initCachingPolicy(CachingPolicy.THROUGH);
+ appManager.save(userAccount1);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("001");
+ appManager.find("001");
+ }
- var userAccount1 = new UserAccount("001", "John", "He is a boy.");
+ /**
+ * Read-through and write-around.
+ */
+ public void useReadThroughAndWriteAroundStrategy() {
+ LOGGER.info("# CachingPolicy.AROUND");
+ appManager.initCachingPolicy(CachingPolicy.AROUND);
- appManager.save(userAccount1);
- LOGGER.info(appManager.printCacheContent());
- appManager.find("001");
- appManager.find("001");
- }
+ var userAccount2 = new UserAccount("002", "Jane", "She is a girl.");
- /**
- * Read-through and write-around.
- */
- public void useReadThroughAndWriteAroundStrategy() {
- LOGGER.info("# CachingPolicy.AROUND");
- appManager.initCachingPolicy(CachingPolicy.AROUND);
+ appManager.save(userAccount2);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("002");
+ LOGGER.info(appManager.printCacheContent());
+ userAccount2 = appManager.find("002");
+ userAccount2.setUserName("Jane G.");
+ appManager.save(userAccount2);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("002");
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("002");
+ }
- var userAccount2 = new UserAccount("002", "Jane", "She is a girl.");
+ /**
+ * Read-through and write-behind.
+ */
+ public void useReadThroughAndWriteBehindStrategy() {
+ LOGGER.info("# CachingPolicy.BEHIND");
+ appManager.initCachingPolicy(CachingPolicy.BEHIND);
- appManager.save(userAccount2);
- LOGGER.info(appManager.printCacheContent());
- appManager.find("002");
- LOGGER.info(appManager.printCacheContent());
- userAccount2 = appManager.find("002");
- userAccount2.setUserName("Jane G.");
- appManager.save(userAccount2);
- LOGGER.info(appManager.printCacheContent());
- appManager.find("002");
- LOGGER.info(appManager.printCacheContent());
- appManager.find("002");
- }
+ var userAccount3 = new UserAccount("003",
+ "Adam",
+ "He likes food.");
+ var userAccount4 = new UserAccount("004",
+ "Rita",
+ "She hates cats.");
+ var userAccount5 = new UserAccount("005",
+ "Isaac",
+ "He is allergic to mustard.");
- /**
- * Read-through and write-behind.
- */
- public void useReadThroughAndWriteBehindStrategy() {
- LOGGER.info("# CachingPolicy.BEHIND");
- appManager.initCachingPolicy(CachingPolicy.BEHIND);
+ appManager.save(userAccount3);
+ appManager.save(userAccount4);
+ appManager.save(userAccount5);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("003");
+ LOGGER.info(appManager.printCacheContent());
+ UserAccount userAccount6 = new UserAccount("006",
+ "Yasha",
+ "She is an only child.");
+ appManager.save(userAccount6);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("004");
+ LOGGER.info(appManager.printCacheContent());
+ }
- var userAccount3 = new UserAccount("003",
- "Adam",
- "He likes food.");
- var userAccount4 = new UserAccount("004",
- "Rita",
- "She hates cats.");
- var userAccount5 = new UserAccount("005",
- "Isaac",
- "He is allergic to mustard.");
+ /**
+ * Cache-Aside.
+ */
+ public void useCacheAsideStategy() {
+ LOGGER.info("# CachingPolicy.ASIDE");
+ appManager.initCachingPolicy(CachingPolicy.ASIDE);
+ LOGGER.info(appManager.printCacheContent());
- appManager.save(userAccount3);
- appManager.save(userAccount4);
- appManager.save(userAccount5);
- LOGGER.info(appManager.printCacheContent());
- appManager.find("003");
- LOGGER.info(appManager.printCacheContent());
- UserAccount userAccount6 = new UserAccount("006",
- "Yasha",
- "She is an only child.");
- appManager.save(userAccount6);
- LOGGER.info(appManager.printCacheContent());
- appManager.find("004");
- LOGGER.info(appManager.printCacheContent());
- }
+ var userAccount3 = new UserAccount("003",
+ "Adam",
+ "He likes food.");
+ var userAccount4 = new UserAccount("004",
+ "Rita",
+ "She hates cats.");
+ var userAccount5 = new UserAccount("005",
+ "Isaac",
+ "He is allergic to mustard.");
+ appManager.save(userAccount3);
+ appManager.save(userAccount4);
+ appManager.save(userAccount5);
- /**
- * Cache-Aside.
- */
- public void useCacheAsideStategy() {
- LOGGER.info("# CachingPolicy.ASIDE");
- appManager.initCachingPolicy(CachingPolicy.ASIDE);
- LOGGER.info(appManager.printCacheContent());
-
- var userAccount3 = new UserAccount("003",
- "Adam",
- "He likes food.");
- var userAccount4 = new UserAccount("004",
- "Rita",
- "She hates cats.");
- var userAccount5 = new UserAccount("005",
- "Isaac",
- "He is allergic to mustard.");
- appManager.save(userAccount3);
- appManager.save(userAccount4);
- appManager.save(userAccount5);
-
- LOGGER.info(appManager.printCacheContent());
- appManager.find("003");
- LOGGER.info(appManager.printCacheContent());
- appManager.find("004");
- LOGGER.info(appManager.printCacheContent());
- }
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("003");
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("004");
+ LOGGER.info(appManager.printCacheContent());
+ }
}
diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java
index ac3100523..1bafa8ff9 100644
--- a/caching/src/main/java/com/iluwatar/caching/AppManager.java
+++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java
@@ -23,9 +23,9 @@
package com.iluwatar.caching;
+import com.iluwatar.caching.database.DbManager;
import java.util.Optional;
-import com.iluwatar.caching.database.DbManager;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@@ -68,7 +68,7 @@ public class AppManager {
* to (temporarily) store the data/objects during runtime.
*/
public void initDb() {
- dbManager.connect();
+ dbManager.connect();
}
/**
diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java
index ca67fd334..d9d2e900b 100644
--- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java
+++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java
@@ -23,11 +23,11 @@
package com.iluwatar.caching;
+import com.iluwatar.caching.database.DbManager;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
-import com.iluwatar.caching.database.DbManager;
import lombok.extern.slf4j.Slf4j;
/**
diff --git a/caching/src/main/java/com/iluwatar/caching/LruCache.java b/caching/src/main/java/com/iluwatar/caching/LruCache.java
index f23eb79b4..a9a93a4f6 100644
--- a/caching/src/main/java/com/iluwatar/caching/LruCache.java
+++ b/caching/src/main/java/com/iluwatar/caching/LruCache.java
@@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
+
/**
* Data structure/implementation of the application's cache. The data structure
* consists of a hash table attached with a doubly linked-list. The linked-list
@@ -62,7 +63,8 @@ public class LruCache {
/**
* Node definition.
- * @param id String
+ *
+ * @param id String
* @param account {@link UserAccount}
*/
Node(final String id, final UserAccount account) {
@@ -90,6 +92,7 @@ public class LruCache {
/**
* Constructor.
+ *
* @param cap Integer.
*/
public LruCache(final int cap) {
@@ -98,6 +101,7 @@ public class LruCache {
/**
* Get user account.
+ *
* @param userId String
* @return {@link UserAccount}
*/
@@ -113,6 +117,7 @@ public class LruCache {
/**
* Remove node from linked list.
+ *
* @param node {@link Node}
*/
public void remove(final Node node) {
@@ -130,6 +135,7 @@ public class LruCache {
/**
* Move node to the front of the list.
+ *
* @param node {@link Node}
*/
public void setHead(final Node node) {
@@ -146,8 +152,9 @@ public class LruCache {
/**
* Set user account.
+ *
* @param userAccount {@link UserAccount}
- * @param userId {@link String}
+ * @param userId {@link String}
*/
public void set(final String userId, final UserAccount userAccount) {
if (cache.containsKey(userId)) {
@@ -169,19 +176,21 @@ public class LruCache {
}
}
- /**
- * Che if Cache cintains the userId.
- * @param userId {@link String}
- * @return boolean
- */
+ /**
+ * Che if Cache cintains the userId.
+ *
+ * @param userId {@link String}
+ * @return boolean
+ */
public boolean contains(final String userId) {
return cache.containsKey(userId);
}
- /**
- * Invalidate cache for user.
- * @param userId {@link String}
- */
+ /**
+ * Invalidate cache for user.
+ *
+ * @param userId {@link String}
+ */
public void invalidate(final String userId) {
var toBeRemoved = cache.remove(userId);
if (toBeRemoved != null) {
@@ -191,18 +200,19 @@ public class LruCache {
}
}
- /**
- * Is cache full?
- * @return boolean
- */
+ /**
+ * Check if the cache is full.
+ * @return boolean
+ */
public boolean isFull() {
return cache.size() >= capacity;
}
- /**
- * Get LRU data.
- * @return {@link UserAccount}
- */
+ /**
+ * Get LRU data.
+ *
+ * @return {@link UserAccount}
+ */
public UserAccount getLruData() {
return end.userAccount;
}
@@ -218,6 +228,7 @@ public class LruCache {
/**
* Returns cache data in list form.
+ *
* @return {@link List}
*/
public List getCacheDataInListForm() {
@@ -232,6 +243,7 @@ public class LruCache {
/**
* Set cache capacity.
+ *
* @param newCapacity int
*/
public void setCapacity(final int newCapacity) {
diff --git a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java
index 496384abc..c8388e93d 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java
@@ -8,33 +8,36 @@ import com.iluwatar.caching.UserAccount;
* and updating data. MongoDB was used as the database for the application.
*/
public interface DbManager {
- /**
- * Connect to DB.
- */
- void connect();
+ /**
+ * Connect to DB.
+ */
+ void connect();
- /**
- * Read from DB.
- * @param userId {@link String}
- * @return {@link UserAccount}
- */
- UserAccount readFromDb(String userId);
- /**
- * Write to DB.
- * @param userAccount {@link UserAccount}
- * @return {@link UserAccount}
- */
- UserAccount writeToDb(UserAccount userAccount);
- /**
- * Update record.
- * @param userAccount {@link UserAccount}
- * @return {@link UserAccount}
- */
- UserAccount updateDb(UserAccount userAccount);
- /**
- * Update record or Insert if not exists.
- * @param userAccount {@link UserAccount}
- * @return {@link UserAccount}
- */
- UserAccount upsertDb(UserAccount userAccount);
+ /**
+ * Read from DB.
+ * @param userId {@link String}
+ * @return {@link UserAccount}
+ */
+ UserAccount readFromDb(String userId);
+
+ /**
+ * Write to DB.
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
+ UserAccount writeToDb(UserAccount userAccount);
+
+ /**
+ * Update record.
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
+ UserAccount updateDb(UserAccount userAccount);
+
+ /**
+ * Update record or Insert if not exists.
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
+ UserAccount upsertDb(UserAccount userAccount);
}
diff --git a/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java b/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java
index f68177203..90ef432cc 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java
@@ -4,22 +4,22 @@ package com.iluwatar.caching.database;
* Creates the database connection accroding the input parameter.
*/
public final class DbManagerFactory {
- /**
- * Private constructor.
- */
- private DbManagerFactory() {
- }
+ /**
+ * Private constructor.
+ */
+ private DbManagerFactory() {
+ }
- /**
- * Init database.
- *
- * @param isMongo boolean
- * @return {@link DbManager}
- */
- public static DbManager initDb(final boolean isMongo) {
- if (isMongo) {
- return new MongoDb();
- }
- return new VirtualDb();
+ /**
+ * Init database.
+ *
+ * @param isMongo boolean
+ * @return {@link DbManager}
+ */
+ public static DbManager initDb(final boolean isMongo) {
+ if (isMongo) {
+ return new MongoDb();
}
+ return new VirtualDb();
+ }
}
diff --git a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
index fceedc9aa..083f3b615 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
@@ -1,5 +1,10 @@
package com.iluwatar.caching.database;
+import static com.iluwatar.caching.constants.CachingConstants.ADD_INFO;
+import static com.iluwatar.caching.constants.CachingConstants.USER_ACCOUNT;
+import static com.iluwatar.caching.constants.CachingConstants.USER_ID;
+import static com.iluwatar.caching.constants.CachingConstants.USER_NAME;
+
import com.iluwatar.caching.UserAccount;
import com.iluwatar.caching.constants.CachingConstants;
import com.mongodb.MongoClient;
@@ -7,114 +12,109 @@ import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.UpdateOptions;
import org.bson.Document;
-import static com.iluwatar.caching.constants.CachingConstants.USER_NAME;
-import static com.iluwatar.caching.constants.CachingConstants.ADD_INFO;
-import static com.iluwatar.caching.constants.CachingConstants.USER_ID;
-import static com.iluwatar.caching.constants.CachingConstants.USER_ACCOUNT;
-
/**
* Implementation of DatabaseManager.
* implements base methods to work with MongoDb.
*/
public class MongoDb implements DbManager {
- /**
- * Mongo db.
- */
- private MongoDatabase db;
+ /**
+ * Mongo db.
+ */
+ private MongoDatabase db;
- /**
- * Connect to Db.
- */
- @Override
- public void connect() {
- MongoClient mongoClient = new MongoClient();
- db = mongoClient.getDatabase("test");
- }
+ /**
+ * Connect to Db.
+ */
+ @Override
+ public void connect() {
+ MongoClient mongoClient = new MongoClient();
+ db = mongoClient.getDatabase("test");
+ }
- /**
- * Read data from DB.
- *
- * @param userId {@link String}
- * @return {@link UserAccount}
- */
- @Override
- public UserAccount readFromDb(final String userId) {
- if (db == null) {
- connect();
- }
- var iterable = db
- .getCollection(CachingConstants.USER_ACCOUNT)
- .find(new Document(USER_ID, userId));
- if (iterable.first()==null) {
- return null;
- }
- Document doc = iterable.first();
- String userName = doc.getString(USER_NAME);
- String appInfo = doc.getString(ADD_INFO);
- return new UserAccount(userId, userName, appInfo);
+ /**
+ * Read data from DB.
+ *
+ * @param userId {@link String}
+ * @return {@link UserAccount}
+ */
+ @Override
+ public UserAccount readFromDb(final String userId) {
+ if (db == null) {
+ connect();
}
+ var iterable = db
+ .getCollection(CachingConstants.USER_ACCOUNT)
+ .find(new Document(USER_ID, userId));
+ if (iterable.first() == null) {
+ return null;
+ }
+ Document doc = iterable.first();
+ String userName = doc.getString(USER_NAME);
+ String appInfo = doc.getString(ADD_INFO);
+ return new UserAccount(userId, userName, appInfo);
+ }
- /**
- * Write data to DB.
- *
- * @param userAccount {@link UserAccount}
- * @return {@link UserAccount}
- */
- @Override
- public UserAccount writeToDb(final UserAccount userAccount) {
- if (db == null) {
- connect();
- }
- db.getCollection(USER_ACCOUNT).insertOne(
- new Document(USER_ID, userAccount.getUserId())
- .append(USER_NAME, userAccount.getUserName())
- .append(ADD_INFO, userAccount.getAdditionalInfo())
- );
- return userAccount;
+ /**
+ * Write data to DB.
+ *
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
+ @Override
+ public UserAccount writeToDb(final UserAccount userAccount) {
+ if (db == null) {
+ connect();
}
+ db.getCollection(USER_ACCOUNT).insertOne(
+ new Document(USER_ID, userAccount.getUserId())
+ .append(USER_NAME, userAccount.getUserName())
+ .append(ADD_INFO, userAccount.getAdditionalInfo())
+ );
+ return userAccount;
+ }
- /**
- * Update DB.
- *
- * @param userAccount {@link UserAccount}
- * @return {@link UserAccount}
- */
- @Override
- public UserAccount updateDb(final UserAccount userAccount) {
- if (db == null) {
- connect();
- }
- Document id = new Document(USER_ID, userAccount.getUserId());
- Document dataSet = new Document(USER_NAME, userAccount.getUserName())
- .append(ADD_INFO, userAccount.getAdditionalInfo());
- db.getCollection(CachingConstants.USER_ACCOUNT)
- .updateOne(id, new Document("$set", dataSet));
- return userAccount;
+ /**
+ * Update DB.
+ *
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
+ @Override
+ public UserAccount updateDb(final UserAccount userAccount) {
+ if (db == null) {
+ connect();
}
+ Document id = new Document(USER_ID, userAccount.getUserId());
+ Document dataSet = new Document(USER_NAME, userAccount.getUserName())
+ .append(ADD_INFO, userAccount.getAdditionalInfo());
+ db.getCollection(CachingConstants.USER_ACCOUNT)
+ .updateOne(id, new Document("$set", dataSet));
+ return userAccount;
+ }
- /**
- * Update data if exists.
- *
- * @param userAccount {@link UserAccount}
- * @return {@link UserAccount}
- */
- @Override
- public UserAccount upsertDb(final UserAccount userAccount) {
- if (db == null) {
- connect();
- }
- String userId = userAccount.getUserId();
- String userName = userAccount.getUserName();
- String additionalInfo = userAccount.getAdditionalInfo();
- db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
- new Document(USER_ID, userId),
- new Document("$set",
- new Document(USER_ID, userId)
- .append(USER_NAME, userName)
- .append(ADD_INFO, additionalInfo)
- ),
- new UpdateOptions().upsert(true)
- );
- return userAccount;
+ /**
+ * Update data if exists.
+ *
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
+ @Override
+ public UserAccount upsertDb(final UserAccount userAccount) {
+ if (db == null) {
+ connect();
}
+ String userId = userAccount.getUserId();
+ String userName = userAccount.getUserName();
+ String additionalInfo = userAccount.getAdditionalInfo();
+ db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
+ new Document(USER_ID, userId),
+ new Document("$set",
+ new Document(USER_ID, userId)
+ .append(USER_NAME, userName)
+ .append(ADD_INFO, additionalInfo)
+ ),
+ new UpdateOptions().upsert(true)
+ );
+ return userAccount;
+ }
}
diff --git a/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java
index bd1601ee7..283dda47a 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java
@@ -10,60 +10,64 @@ import java.util.Map;
* implements base methods to work with hashMap as database.
*/
public class VirtualDb implements DbManager {
- /**
- * Virtual DataBase.
- */
- private Map db;
+ /**
+ * Virtual DataBase.
+ */
+ private Map db;
- /**
- * Creates new HashMap.
- */
- @Override
- public void connect() {
- db = new HashMap<>();
- }
+ /**
+ * Creates new HashMap.
+ */
+ @Override
+ public void connect() {
+ db = new HashMap<>();
+ }
- /**
- * Read from Db.
- * @param userId {@link String}
- * @return {@link UserAccount}
- */
- @Override
- public UserAccount readFromDb(final String userId) {
- if (db.containsKey(userId)) {
- return db.get(userId);
- }
- return null;
+ /**
+ * Read from Db.
+ *
+ * @param userId {@link String}
+ * @return {@link UserAccount}
+ */
+ @Override
+ public UserAccount readFromDb(final String userId) {
+ if (db.containsKey(userId)) {
+ return db.get(userId);
}
+ return null;
+ }
- /**
- * Write to DB.
- * @param userAccount {@link UserAccount}
- * @return {@link UserAccount}
- */
- @Override
- public UserAccount writeToDb(final UserAccount userAccount) {
- db.put(userAccount.getUserId(), userAccount);
- return userAccount;
- }
+ /**
+ * Write to DB.
+ *
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
+ @Override
+ public UserAccount writeToDb(final UserAccount userAccount) {
+ db.put(userAccount.getUserId(), userAccount);
+ return userAccount;
+ }
- /**
- * Update reecord in DB.
- * @param userAccount {@link UserAccount}
- * @return {@link UserAccount}
- */
- @Override
- public UserAccount updateDb(final UserAccount userAccount) {
- return writeToDb(userAccount);
- }
+ /**
+ * Update reecord in DB.
+ *
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
+ @Override
+ public UserAccount updateDb(final UserAccount userAccount) {
+ return writeToDb(userAccount);
+ }
- /**
- * Update.
- * @param userAccount {@link UserAccount}
- * @return {@link UserAccount}
- */
- @Override
- public UserAccount upsertDb(final UserAccount userAccount) {
- return updateDb(userAccount);
- }
+ /**
+ * Update.
+ *
+ * @param userAccount {@link UserAccount}
+ * @return {@link UserAccount}
+ */
+ @Override
+ public UserAccount upsertDb(final UserAccount userAccount) {
+ return updateDb(userAccount);
+ }
}
diff --git a/caching/src/main/java/com/iluwatar/caching/package-info.java b/caching/src/main/java/com/iluwatar/caching/package-info.java
index fed1ab2a8..00687084f 100644
--- a/caching/src/main/java/com/iluwatar/caching/package-info.java
+++ b/caching/src/main/java/com/iluwatar/caching/package-info.java
@@ -1,17 +1,14 @@
/**
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
- *
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
- *
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
- *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
From 50755b7215bc3d2ce4cf67cc72dadeab86ba2d49 Mon Sep 17 00:00:00 2001
From: Victor Zalevskii
Date: Mon, 30 Aug 2021 08:48:03 +0300
Subject: [PATCH 08/25] Fix Bug with mongo connection. Used "Try with
resources"
---
.../java/com/iluwatar/caching/AppManager.java | 7 +-
.../iluwatar/caching/database/DbManager.java | 3 +-
.../iluwatar/caching/database/MongoDb.java | 109 ++++++++++--------
.../DatabaseConnectionException.java | 7 ++
4 files changed, 73 insertions(+), 53 deletions(-)
create mode 100644 caching/src/main/java/com/iluwatar/caching/database/exceptions/DatabaseConnectionException.java
diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java
index 1bafa8ff9..19773f6ad 100644
--- a/caching/src/main/java/com/iluwatar/caching/AppManager.java
+++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java
@@ -24,6 +24,7 @@
package com.iluwatar.caching;
import com.iluwatar.caching.database.DbManager;
+import com.iluwatar.caching.database.exceptions.DatabaseConnectionException;
import java.util.Optional;
import lombok.Data;
@@ -68,7 +69,11 @@ public class AppManager {
* to (temporarily) store the data/objects during runtime.
*/
public void initDb() {
- dbManager.connect();
+ try {
+ dbManager.connect();
+ } catch (DatabaseConnectionException e) {
+ LOGGER.error("Could not connect to DB: {}", e.getMessage());
+ }
}
/**
diff --git a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java
index c8388e93d..df6219e63 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java
@@ -1,6 +1,7 @@
package com.iluwatar.caching.database;
import com.iluwatar.caching.UserAccount;
+import com.iluwatar.caching.database.exceptions.DatabaseConnectionException;
/**
* DBManager handles the communication with the underlying data store i.e.
@@ -11,7 +12,7 @@ public interface DbManager {
/**
* Connect to DB.
*/
- void connect();
+ void connect() throws DatabaseConnectionException;
/**
* Read from DB.
diff --git a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
index 083f3b615..1993e01d8 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
@@ -7,28 +7,31 @@ import static com.iluwatar.caching.constants.CachingConstants.USER_NAME;
import com.iluwatar.caching.UserAccount;
import com.iluwatar.caching.constants.CachingConstants;
+import com.iluwatar.caching.database.exceptions.DatabaseConnectionException;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.UpdateOptions;
+import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
/**
* Implementation of DatabaseManager.
* implements base methods to work with MongoDb.
*/
+@Slf4j
public class MongoDb implements DbManager {
- /**
- * Mongo db.
- */
- private MongoDatabase db;
+ private static final String DATABASE_NAME = "test";
/**
- * Connect to Db.
+ * Connect to Db. Check th connection
*/
@Override
- public void connect() {
- MongoClient mongoClient = new MongoClient();
- db = mongoClient.getDatabase("test");
+ public void connect() throws DatabaseConnectionException {
+ try (MongoClient mongoClient = new MongoClient()) {
+ mongoClient.getDatabase("test");
+ } catch (NoClassDefFoundError e) {
+ throw new DatabaseConnectionException("Could not connect to DB.");
+ }
}
/**
@@ -39,19 +42,23 @@ public class MongoDb implements DbManager {
*/
@Override
public UserAccount readFromDb(final String userId) {
- if (db == null) {
- connect();
+ try (MongoClient mongoClient = new MongoClient()) {
+ MongoDatabase db = mongoClient.getDatabase(DATABASE_NAME);
+ var iterable = db
+ .getCollection(CachingConstants.USER_ACCOUNT)
+ .find(new Document(USER_ID, userId));
+ if (iterable.first() == null) {
+ return null;
+ }
+ Document doc = iterable.first();
+ if (doc != null) {
+ String userName = doc.getString(USER_NAME);
+ String appInfo = doc.getString(ADD_INFO);
+ return new UserAccount(userId, userName, appInfo);
+ } else {
+ return null;
+ }
}
- var iterable = db
- .getCollection(CachingConstants.USER_ACCOUNT)
- .find(new Document(USER_ID, userId));
- if (iterable.first() == null) {
- return null;
- }
- Document doc = iterable.first();
- String userName = doc.getString(USER_NAME);
- String appInfo = doc.getString(ADD_INFO);
- return new UserAccount(userId, userName, appInfo);
}
/**
@@ -62,15 +69,15 @@ public class MongoDb implements DbManager {
*/
@Override
public UserAccount writeToDb(final UserAccount userAccount) {
- if (db == null) {
- connect();
+ try (MongoClient mongoClient = new MongoClient()) {
+ MongoDatabase db = mongoClient.getDatabase(DATABASE_NAME);
+ db.getCollection(USER_ACCOUNT).insertOne(
+ new Document(USER_ID, userAccount.getUserId())
+ .append(USER_NAME, userAccount.getUserName())
+ .append(ADD_INFO, userAccount.getAdditionalInfo())
+ );
+ return userAccount;
}
- db.getCollection(USER_ACCOUNT).insertOne(
- new Document(USER_ID, userAccount.getUserId())
- .append(USER_NAME, userAccount.getUserName())
- .append(ADD_INFO, userAccount.getAdditionalInfo())
- );
- return userAccount;
}
/**
@@ -81,15 +88,15 @@ public class MongoDb implements DbManager {
*/
@Override
public UserAccount updateDb(final UserAccount userAccount) {
- if (db == null) {
- connect();
+ try (MongoClient mongoClient = new MongoClient()) {
+ MongoDatabase db = mongoClient.getDatabase(DATABASE_NAME);
+ Document id = new Document(USER_ID, userAccount.getUserId());
+ Document dataSet = new Document(USER_NAME, userAccount.getUserName())
+ .append(ADD_INFO, userAccount.getAdditionalInfo());
+ db.getCollection(CachingConstants.USER_ACCOUNT)
+ .updateOne(id, new Document("$set", dataSet));
+ return userAccount;
}
- Document id = new Document(USER_ID, userAccount.getUserId());
- Document dataSet = new Document(USER_NAME, userAccount.getUserName())
- .append(ADD_INFO, userAccount.getAdditionalInfo());
- db.getCollection(CachingConstants.USER_ACCOUNT)
- .updateOne(id, new Document("$set", dataSet));
- return userAccount;
}
/**
@@ -100,21 +107,21 @@ public class MongoDb implements DbManager {
*/
@Override
public UserAccount upsertDb(final UserAccount userAccount) {
- if (db == null) {
- connect();
+ try (MongoClient mongoClient = new MongoClient()) {
+ MongoDatabase db = mongoClient.getDatabase(DATABASE_NAME);
+ String userId = userAccount.getUserId();
+ String userName = userAccount.getUserName();
+ String additionalInfo = userAccount.getAdditionalInfo();
+ db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
+ new Document(USER_ID, userId),
+ new Document("$set",
+ new Document(USER_ID, userId)
+ .append(USER_NAME, userName)
+ .append(ADD_INFO, additionalInfo)
+ ),
+ new UpdateOptions().upsert(true)
+ );
+ return userAccount;
}
- String userId = userAccount.getUserId();
- String userName = userAccount.getUserName();
- String additionalInfo = userAccount.getAdditionalInfo();
- db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
- new Document(USER_ID, userId),
- new Document("$set",
- new Document(USER_ID, userId)
- .append(USER_NAME, userName)
- .append(ADD_INFO, additionalInfo)
- ),
- new UpdateOptions().upsert(true)
- );
- return userAccount;
}
}
diff --git a/caching/src/main/java/com/iluwatar/caching/database/exceptions/DatabaseConnectionException.java b/caching/src/main/java/com/iluwatar/caching/database/exceptions/DatabaseConnectionException.java
new file mode 100644
index 000000000..971ee9e09
--- /dev/null
+++ b/caching/src/main/java/com/iluwatar/caching/database/exceptions/DatabaseConnectionException.java
@@ -0,0 +1,7 @@
+package com.iluwatar.caching.database.exceptions;
+
+public class DatabaseConnectionException extends Exception {
+ public DatabaseConnectionException(String s) {
+ super(s);
+ }
+}
From c0e4bf3d1dc72a8b7a75d0dc750d9407d9bd3ab8 Mon Sep 17 00:00:00 2001
From: Victor Zalevskii
Date: Wed, 1 Sep 2021 10:20:35 +0300
Subject: [PATCH 09/25] Add test
---
.../src/test/java/com/iluwatar/caching/AppTest.java | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/caching/src/test/java/com/iluwatar/caching/AppTest.java b/caching/src/test/java/com/iluwatar/caching/AppTest.java
index 12b72d56a..f83eefa0e 100644
--- a/caching/src/test/java/com/iluwatar/caching/AppTest.java
+++ b/caching/src/test/java/com/iluwatar/caching/AppTest.java
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Tests that Caching example runs without errors.
@@ -43,7 +44,14 @@ class AppTest {
@Test
void shouldExecuteApplicationWithoutException() {
-
assertDoesNotThrow(() -> App.main(new String[]{}));
}
+
+ @Test
+ void executeAppWithException(){
+ assertThrows(
+ NoClassDefFoundError.class,
+ () -> App.main(new String[]{"--mongo"})
+ );
+ }
}
From 6adb27ca3d12b36b3e57d70540165424736930df Mon Sep 17 00:00:00 2001
From: Victor Zalevskii
Date: Thu, 2 Sep 2021 15:24:08 +0300
Subject: [PATCH 10/25] Added docker-compose for mongo db. MongoDb db work
fixed.
---
caching/docker-compose.yml | 11 ++
caching/pom.xml | 22 ++--
.../main/java/com/iluwatar/caching/App.java | 8 +-
.../java/com/iluwatar/caching/AppManager.java | 31 ++---
.../java/com/iluwatar/caching/CacheStore.java | 1 +
.../com/iluwatar/caching/UserAccount.java | 8 +-
.../iluwatar/caching/database/DbManager.java | 12 +-
.../iluwatar/caching/database/MongoDb.java | 117 +++++++++---------
.../iluwatar/caching/database/VirtualDb.java | 5 +
.../DatabaseConnectionException.java | 7 --
10 files changed, 123 insertions(+), 99 deletions(-)
create mode 100644 caching/docker-compose.yml
delete mode 100644 caching/src/main/java/com/iluwatar/caching/database/exceptions/DatabaseConnectionException.java
diff --git a/caching/docker-compose.yml b/caching/docker-compose.yml
new file mode 100644
index 000000000..6b6494690
--- /dev/null
+++ b/caching/docker-compose.yml
@@ -0,0 +1,11 @@
+version: '3.7'
+services:
+ mongodb_container:
+ image: mongo:latest
+ environment:
+ MONGO_INITDB_ROOT_USERNAME: root
+ MONGO_INITDB_ROOT_PASSWORD: rootpassword
+ ports:
+ - 27017:27017
+ volumes:
+ - ./mongo-data/:/data/db
\ No newline at end of file
diff --git a/caching/pom.xml b/caching/pom.xml
index 0491bb4e1..e56da709f 100644
--- a/caching/pom.xml
+++ b/caching/pom.xml
@@ -39,19 +39,21 @@
test
- org.mongodb
- mongodb-driver
- 3.12.1
+ org.mockito
+ mockito-junit-jupiter
+ 3.12.4
+ test
+
+
+ org.mockito
+ mockito-all
+ 1.10.19
+ test
org.mongodb
- mongodb-driver-core
- 3.0.4
-
-
- org.mongodb
- bson
- 3.0.4
+ mongo-java-driver
+ 3.4.1
AppManager --> CacheStore/LRUCache/CachingPolicy -->
- * DBManager}
+ * DBManager}
*
*
*
- * To run the application with MongoDb, just start it with parameter --mongo
- * Example: java -jar app.jar --mongo
+ * To run the application with MongoDb, just start it with parameter --mongo
+ * Example: java -jar app.jar --mongo
*
*
* @see CacheStore
@@ -65,6 +65,7 @@ public class App {
/**
* Constructor of current App.
+ *
* @param isMongo boolean
*/
public App(final boolean isMongo) {
@@ -97,6 +98,7 @@ public class App {
/**
* Check the input parameters. if
+ *
* @param args input params
* @return true if there is "--mongo" parameter in arguments
*/
diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java
index 19773f6ad..53489c83b 100644
--- a/caching/src/main/java/com/iluwatar/caching/AppManager.java
+++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java
@@ -24,10 +24,9 @@
package com.iluwatar.caching;
import com.iluwatar.caching.database.DbManager;
-import com.iluwatar.caching.database.exceptions.DatabaseConnectionException;
+
import java.util.Optional;
-import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
@@ -39,7 +38,6 @@ import lombok.extern.slf4j.Slf4j;
* appropriate function in the CacheStore class.
*/
@Slf4j
-@Data
public class AppManager {
/**
* Caching Policy.
@@ -56,6 +54,7 @@ public class AppManager {
/**
* Constructor.
+ *
* @param newDbManager database manager
*/
public AppManager(final DbManager newDbManager) {
@@ -69,15 +68,12 @@ public class AppManager {
* to (temporarily) store the data/objects during runtime.
*/
public void initDb() {
- try {
- dbManager.connect();
- } catch (DatabaseConnectionException e) {
- LOGGER.error("Could not connect to DB: {}", e.getMessage());
- }
+ dbManager.connect();
}
/**
* Initialize caching policy.
+ *
* @param policy is a {@link CachingPolicy}
*/
public void initCachingPolicy(final CachingPolicy policy) {
@@ -90,6 +86,7 @@ public class AppManager {
/**
* Find user account.
+ *
* @param userId String
* @return {@link UserAccount}
*/
@@ -108,6 +105,7 @@ public class AppManager {
/**
* Save user account.
+ *
* @param userAccount {@link UserAccount}
*/
public void save(final UserAccount userAccount) {
@@ -125,6 +123,7 @@ public class AppManager {
/**
* Returns String.
+ *
* @return String
*/
public String printCacheContent() {
@@ -133,6 +132,7 @@ public class AppManager {
/**
* Cache-Aside save user account helper.
+ *
* @param userAccount {@link UserAccount}
*/
private void saveAside(final UserAccount userAccount) {
@@ -142,17 +142,18 @@ public class AppManager {
/**
* Cache-Aside find user account helper.
+ *
* @param userId String
* @return {@link UserAccount}
*/
private UserAccount findAside(final String userId) {
return Optional.ofNullable(cacheStore.get(userId))
- .or(() -> {
- Optional userAccount =
- Optional.ofNullable(dbManager.readFromDb(userId));
- userAccount.ifPresent(account -> cacheStore.set(userId, account));
- return userAccount;
- })
- .orElse(null);
+ .or(() -> {
+ Optional userAccount =
+ Optional.ofNullable(dbManager.readFromDb(userId));
+ userAccount.ifPresent(account -> cacheStore.set(userId, account));
+ return userAccount;
+ })
+ .orElse(null);
}
}
diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java
index d9d2e900b..589e9eca0 100644
--- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java
+++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java
@@ -166,6 +166,7 @@ public class CacheStore {
.map(LruCache::getCacheDataInListForm)
.orElse(List.of())
.forEach(dbManager::updateDb);
+ dbManager.disconnect();
}
/**
diff --git a/caching/src/main/java/com/iluwatar/caching/UserAccount.java b/caching/src/main/java/com/iluwatar/caching/UserAccount.java
index 800f14c9e..1ec3af228 100644
--- a/caching/src/main/java/com/iluwatar/caching/UserAccount.java
+++ b/caching/src/main/java/com/iluwatar/caching/UserAccount.java
@@ -24,17 +24,17 @@
package com.iluwatar.caching;
import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.Setter;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* Entity class (stored in cache and DB) used in the application.
*/
-@Setter
-@Getter
+@Data
@AllArgsConstructor
@ToString
+@EqualsAndHashCode
public class UserAccount {
/**
* User Id.
diff --git a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java
index df6219e63..14d7247f8 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java
@@ -1,7 +1,6 @@
package com.iluwatar.caching.database;
import com.iluwatar.caching.UserAccount;
-import com.iluwatar.caching.database.exceptions.DatabaseConnectionException;
/**
* DBManager handles the communication with the underlying data store i.e.
@@ -12,10 +11,16 @@ public interface DbManager {
/**
* Connect to DB.
*/
- void connect() throws DatabaseConnectionException;
+ void connect();
+
+ /**
+ * Disconnect from DB.
+ */
+ void disconnect();
/**
* Read from DB.
+ *
* @param userId {@link String}
* @return {@link UserAccount}
*/
@@ -23,6 +28,7 @@ public interface DbManager {
/**
* Write to DB.
+ *
* @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
@@ -30,6 +36,7 @@ public interface DbManager {
/**
* Update record.
+ *
* @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
@@ -37,6 +44,7 @@ public interface DbManager {
/**
* Update record or Insert if not exists.
+ *
* @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
diff --git a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
index 1993e01d8..a9dd006f8 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java
@@ -7,10 +7,13 @@ import static com.iluwatar.caching.constants.CachingConstants.USER_NAME;
import com.iluwatar.caching.UserAccount;
import com.iluwatar.caching.constants.CachingConstants;
-import com.iluwatar.caching.database.exceptions.DatabaseConnectionException;
import com.mongodb.MongoClient;
+import com.mongodb.MongoClientOptions;
+import com.mongodb.MongoCredential;
+import com.mongodb.ServerAddress;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.UpdateOptions;
+import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
@@ -20,18 +23,28 @@ import org.bson.Document;
*/
@Slf4j
public class MongoDb implements DbManager {
- private static final String DATABASE_NAME = "test";
+ private static final String DATABASE_NAME = "admin";
+ private static final String MONGO_USER = "root";
+ private static final String MONGO_PASSWORD = "rootpassword";
+ private MongoClient client;
+ private MongoDatabase db;
/**
* Connect to Db. Check th connection
*/
@Override
- public void connect() throws DatabaseConnectionException {
- try (MongoClient mongoClient = new MongoClient()) {
- mongoClient.getDatabase("test");
- } catch (NoClassDefFoundError e) {
- throw new DatabaseConnectionException("Could not connect to DB.");
- }
+ public void connect() {
+ MongoCredential mongoCredential = MongoCredential.createCredential(MONGO_USER,
+ DATABASE_NAME,
+ MONGO_PASSWORD.toCharArray());
+ MongoClientOptions options = MongoClientOptions.builder().build();
+ client = new MongoClient(new ServerAddress(), List.of(mongoCredential), options);
+ db = client.getDatabase(DATABASE_NAME);
+ }
+
+ @Override
+ public void disconnect() {
+ client.close();
}
/**
@@ -42,22 +55,19 @@ public class MongoDb implements DbManager {
*/
@Override
public UserAccount readFromDb(final String userId) {
- try (MongoClient mongoClient = new MongoClient()) {
- MongoDatabase db = mongoClient.getDatabase(DATABASE_NAME);
- var iterable = db
- .getCollection(CachingConstants.USER_ACCOUNT)
- .find(new Document(USER_ID, userId));
- if (iterable.first() == null) {
- return null;
- }
- Document doc = iterable.first();
- if (doc != null) {
- String userName = doc.getString(USER_NAME);
- String appInfo = doc.getString(ADD_INFO);
- return new UserAccount(userId, userName, appInfo);
- } else {
- return null;
- }
+ var iterable = db
+ .getCollection(CachingConstants.USER_ACCOUNT)
+ .find(new Document(USER_ID, userId));
+ if (iterable.first() == null) {
+ return null;
+ }
+ Document doc = iterable.first();
+ if (doc != null) {
+ String userName = doc.getString(USER_NAME);
+ String appInfo = doc.getString(ADD_INFO);
+ return new UserAccount(userId, userName, appInfo);
+ } else {
+ return null;
}
}
@@ -69,15 +79,12 @@ public class MongoDb implements DbManager {
*/
@Override
public UserAccount writeToDb(final UserAccount userAccount) {
- try (MongoClient mongoClient = new MongoClient()) {
- MongoDatabase db = mongoClient.getDatabase(DATABASE_NAME);
- db.getCollection(USER_ACCOUNT).insertOne(
- new Document(USER_ID, userAccount.getUserId())
- .append(USER_NAME, userAccount.getUserName())
- .append(ADD_INFO, userAccount.getAdditionalInfo())
- );
- return userAccount;
- }
+ db.getCollection(USER_ACCOUNT).insertOne(
+ new Document(USER_ID, userAccount.getUserId())
+ .append(USER_NAME, userAccount.getUserName())
+ .append(ADD_INFO, userAccount.getAdditionalInfo())
+ );
+ return userAccount;
}
/**
@@ -88,15 +95,12 @@ public class MongoDb implements DbManager {
*/
@Override
public UserAccount updateDb(final UserAccount userAccount) {
- try (MongoClient mongoClient = new MongoClient()) {
- MongoDatabase db = mongoClient.getDatabase(DATABASE_NAME);
- Document id = new Document(USER_ID, userAccount.getUserId());
- Document dataSet = new Document(USER_NAME, userAccount.getUserName())
- .append(ADD_INFO, userAccount.getAdditionalInfo());
- db.getCollection(CachingConstants.USER_ACCOUNT)
- .updateOne(id, new Document("$set", dataSet));
- return userAccount;
- }
+ Document id = new Document(USER_ID, userAccount.getUserId());
+ Document dataSet = new Document(USER_NAME, userAccount.getUserName())
+ .append(ADD_INFO, userAccount.getAdditionalInfo());
+ db.getCollection(CachingConstants.USER_ACCOUNT)
+ .updateOne(id, new Document("$set", dataSet));
+ return userAccount;
}
/**
@@ -107,21 +111,18 @@ public class MongoDb implements DbManager {
*/
@Override
public UserAccount upsertDb(final UserAccount userAccount) {
- try (MongoClient mongoClient = new MongoClient()) {
- MongoDatabase db = mongoClient.getDatabase(DATABASE_NAME);
- String userId = userAccount.getUserId();
- String userName = userAccount.getUserName();
- String additionalInfo = userAccount.getAdditionalInfo();
- db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
- new Document(USER_ID, userId),
- new Document("$set",
- new Document(USER_ID, userId)
- .append(USER_NAME, userName)
- .append(ADD_INFO, additionalInfo)
- ),
- new UpdateOptions().upsert(true)
- );
- return userAccount;
- }
+ String userId = userAccount.getUserId();
+ String userName = userAccount.getUserName();
+ String additionalInfo = userAccount.getAdditionalInfo();
+ db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
+ new Document(USER_ID, userId),
+ new Document("$set",
+ new Document(USER_ID, userId)
+ .append(USER_NAME, userName)
+ .append(ADD_INFO, additionalInfo)
+ ),
+ new UpdateOptions().upsert(true)
+ );
+ return userAccount;
}
}
diff --git a/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java
index 283dda47a..0c9a14ec9 100644
--- a/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java
+++ b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java
@@ -23,6 +23,11 @@ public class VirtualDb implements DbManager {
db = new HashMap<>();
}
+ @Override
+ public void disconnect() {
+ db = null;
+ }
+
/**
* Read from Db.
*
diff --git a/caching/src/main/java/com/iluwatar/caching/database/exceptions/DatabaseConnectionException.java b/caching/src/main/java/com/iluwatar/caching/database/exceptions/DatabaseConnectionException.java
deleted file mode 100644
index 971ee9e09..000000000
--- a/caching/src/main/java/com/iluwatar/caching/database/exceptions/DatabaseConnectionException.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.iluwatar.caching.database.exceptions;
-
-public class DatabaseConnectionException extends Exception {
- public DatabaseConnectionException(String s) {
- super(s);
- }
-}
From 45af6987c10828802e5f40dd3ee3a9f2d0cc0bb1 Mon Sep 17 00:00:00 2001
From: Victor Zalevskii
Date: Thu, 2 Sep 2021 15:30:07 +0300
Subject: [PATCH 11/25] Provided missing tests
---
.../java/com/iluwatar/caching/AppTest.java | 14 +---
.../com/iluwatar/caching/CachingTest.java | 12 +--
.../caching/database/MongoDbTest.java | 84 +++++++++++++++++++
3 files changed, 91 insertions(+), 19 deletions(-)
create mode 100644 caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java
diff --git a/caching/src/test/java/com/iluwatar/caching/AppTest.java b/caching/src/test/java/com/iluwatar/caching/AppTest.java
index f83eefa0e..a50b687c2 100644
--- a/caching/src/test/java/com/iluwatar/caching/AppTest.java
+++ b/caching/src/test/java/com/iluwatar/caching/AppTest.java
@@ -25,19 +25,15 @@ package com.iluwatar.caching;
import org.junit.jupiter.api.Test;
-import java.io.IOException;
-
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Tests that Caching example runs without errors.
*/
class AppTest {
-
/**
* Issue: Add at least one assertion to this test case.
- *
+ *
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@@ -46,12 +42,4 @@ class AppTest {
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
-
- @Test
- void executeAppWithException(){
- assertThrows(
- NoClassDefFoundError.class,
- () -> App.main(new String[]{"--mongo"})
- );
- }
}
diff --git a/caching/src/test/java/com/iluwatar/caching/CachingTest.java b/caching/src/test/java/com/iluwatar/caching/CachingTest.java
index 8a02869bc..0c70e24de 100644
--- a/caching/src/test/java/com/iluwatar/caching/CachingTest.java
+++ b/caching/src/test/java/com/iluwatar/caching/CachingTest.java
@@ -23,11 +23,11 @@
package com.iluwatar.caching;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
/**
* Application test
*/
@@ -48,25 +48,25 @@ class CachingTest {
@Test
void testReadAndWriteThroughStrategy() {
- assertNotNull(app);
+ assertNotNull(app);
app.useReadAndWriteThroughStrategy();
}
@Test
void testReadThroughAndWriteAroundStrategy() {
- assertNotNull(app);
+ assertNotNull(app);
app.useReadThroughAndWriteAroundStrategy();
}
@Test
void testReadThroughAndWriteBehindStrategy() {
- assertNotNull(app);
+ assertNotNull(app);
app.useReadThroughAndWriteBehindStrategy();
}
@Test
void testCacheAsideStrategy() {
- assertNotNull(app);
+ assertNotNull(app);
app.useCacheAsideStategy();
}
}
diff --git a/caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java b/caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java
new file mode 100644
index 000000000..cfb2b718f
--- /dev/null
+++ b/caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java
@@ -0,0 +1,84 @@
+package com.iluwatar.caching.database;
+
+import com.iluwatar.caching.UserAccount;
+import com.iluwatar.caching.constants.CachingConstants;
+import com.mongodb.client.FindIterable;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import org.bson.Document;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.internal.util.reflection.Whitebox;
+
+import static com.iluwatar.caching.constants.CachingConstants.ADD_INFO;
+import static com.iluwatar.caching.constants.CachingConstants.USER_ID;
+import static com.iluwatar.caching.constants.CachingConstants.USER_NAME;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+class MongoDbTest {
+ private static final String ID = "123";
+ private static final String NAME = "Some user";
+ private static final String ADDITIONAL_INFO = "Some app Info";
+
+ @Mock
+ MongoDatabase db;
+ private MongoDb mongoDb = new MongoDb();
+
+ private UserAccount userAccount;
+
+ @BeforeEach
+ void init() {
+ db = mock(MongoDatabase.class);
+ Whitebox.setInternalState(mongoDb, "db", db);
+ userAccount = new UserAccount(ID, NAME, ADDITIONAL_INFO);
+
+
+ }
+
+ @Test
+ void connect() {
+ assertDoesNotThrow(() -> mongoDb.connect());
+ }
+
+ @Test
+ void readFromDb() {
+ Document document = new Document(USER_ID, ID)
+ .append(USER_NAME, NAME)
+ .append(ADD_INFO, ADDITIONAL_INFO);
+ MongoCollection mongoCollection = mock(MongoCollection.class);
+ when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection);
+
+ FindIterable findIterable = mock(FindIterable.class);
+ when(mongoCollection.find(any(Document.class))).thenReturn(findIterable);
+
+ when(findIterable.first()).thenReturn(document);
+
+ assertEquals(mongoDb.readFromDb(ID),userAccount);
+ }
+
+ @Test
+ void writeToDb() {
+ MongoCollection mongoCollection = mock(MongoCollection.class);
+ when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection);
+ doNothing().when(mongoCollection).insertOne(any(Document.class));
+ assertDoesNotThrow(()-> {mongoDb.writeToDb(userAccount);});
+ }
+
+ @Test
+ void updateDb() {
+ MongoCollection mongoCollection = mock(MongoCollection.class);
+ when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection);
+ assertDoesNotThrow(()-> {mongoDb.updateDb(userAccount);});
+ }
+
+ @Test
+ void upsertDb() {
+ MongoCollection mongoCollection = mock(MongoCollection.class);
+ when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection);
+ assertDoesNotThrow(()-> {mongoDb.upsertDb(userAccount);});
+ }
+}
\ No newline at end of file
From 2a01563d218e62cd49c367898d404bd1b3eff494 Mon Sep 17 00:00:00 2001
From: Victor Zalevskii
Date: Thu, 2 Sep 2021 15:33:06 +0300
Subject: [PATCH 12/25] Comments to start Application with mongo.
---
caching/src/main/java/com/iluwatar/caching/App.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java
index c08a095af..6b3930614 100644
--- a/caching/src/main/java/com/iluwatar/caching/App.java
+++ b/caching/src/main/java/com/iluwatar/caching/App.java
@@ -44,7 +44,9 @@ import lombok.extern.slf4j.Slf4j;
*
*
*
- * To run the application with MongoDb, just start it with parameter --mongo
+ * To run the application with MongoDb:
+ * 1. Launch mongoDB in docker container with command: docker-compose up
+ * 2. Start application with parameter --mongo
* Example: java -jar app.jar --mongo
*
*
From be59e5020525e018da11787e5130f74b0cb60dba Mon Sep 17 00:00:00 2001
From: Alain
Date: Tue, 28 Sep 2021 18:19:30 +0200
Subject: [PATCH 13/25] doc: Fix Typos in French local doc (#1818)
Co-authored-by: Subhrodip Mohanta
---
localization/fr/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/localization/fr/README.md b/localization/fr/README.md
index 1e09ce445..cfc9b55d9 100644
--- a/localization/fr/README.md
+++ b/localization/fr/README.md
@@ -48,7 +48,7 @@ que lorsqu’ils sont nécessaires pour une extensibilité pratique.
Une fois que vous êtes familiarisé avec ces concepts, vous pouvez commencer à explorer les
[modèles de conception disponibles](https://java-design-patterns.com/patterns/)
-par n’importe laquelle les approches suivantes :
+par n’importe laquelle des approches suivantes :
- Recherchez un modèle spécifique par son nom.
Vous n’en trouvez pas ? Veuillez signaler un nouveau modèle [ici](https://github.com/iluwatar/java-design-patterns/issues).
From be25c0b4331cb5b801afbe6e9be7bce1f5e47e05 Mon Sep 17 00:00:00 2001
From: Nagaraj Tantri
Date: Wed, 29 Sep 2021 00:09:19 +0530
Subject: [PATCH 14/25] bug-fix: Use Junit5 in the serverless module tests
(#1794)
* #1667: Fixing the serverless tests to use Junit5 and also modifying other classes to remove the deprecated initMock() method
* #1667: Fixing the sonar code smells
Co-authored-by: Subhrodip Mohanta
---
.../microservices/AggregatorTest.java | 2 +-
.../iluwatar/api/gateway/ApiGatewayTest.java | 2 +-
.../invocation/ThreadAsyncExecutorTest.java | 2 +-
.../com/iluwatar/databus/DataBusTest.java | 2 +-
pom.xml | 4 +--
serverless/pom.xml | 26 +++++------------
.../baas/api/FindPersonApiHandlerTest.java | 19 ++++++------
.../baas/api/SavePersonApiHandlerTest.java | 29 ++++++++++---------
8 files changed, 39 insertions(+), 47 deletions(-)
diff --git a/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java b/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java
index 2ffa9cd42..fa229832b 100644
--- a/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java
+++ b/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java
@@ -48,7 +48,7 @@ class AggregatorTest {
@BeforeEach
public void setup() {
- MockitoAnnotations.initMocks(this);
+ MockitoAnnotations.openMocks(this);
}
/**
diff --git a/api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java b/api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java
index 94e76583e..2f71ee4f4 100644
--- a/api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java
+++ b/api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java
@@ -48,7 +48,7 @@ class ApiGatewayTest {
@BeforeEach
public void setup() {
- MockitoAnnotations.initMocks(this);
+ MockitoAnnotations.openMocks(this);
}
/**
diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java
index b644e0e23..495cb6b6e 100644
--- a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java
+++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java
@@ -68,7 +68,7 @@ class ThreadAsyncExecutorTest {
@BeforeEach
void setUp() {
- MockitoAnnotations.initMocks(this);
+ MockitoAnnotations.openMocks(this);
}
/**
diff --git a/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java b/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java
index 9278c965b..ca1c936d0 100644
--- a/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java
+++ b/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java
@@ -46,7 +46,7 @@ class DataBusTest {
@BeforeEach
void setUp() {
- MockitoAnnotations.initMocks(this);
+ MockitoAnnotations.openMocks(this);
}
@Test
diff --git a/pom.xml b/pom.xml
index cfd0de127..cb726a43e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,7 +62,7 @@
1.7.30
1.2.3
1.1.0
- 1.11.289
+ 1.12.13
2.0.1
2.12.3
2.3.1
@@ -72,7 +72,7 @@
2.0.0
3.5.0
1.18.14
- 1.10.21
+ 1.11.5
3.27.0-GA
3.0.0-M5
3.1.0
diff --git a/serverless/pom.xml b/serverless/pom.xml
index 1cf2cb8a5..8ee367fa3 100644
--- a/serverless/pom.xml
+++ b/serverless/pom.xml
@@ -44,16 +44,6 @@
com.amazonaws
aws-java-sdk-dynamodb
${aws-java-sdk-dynamodb.version}
-
-
- com.amazonaws
- aws-java-sdk-s3
-
-
- com.amazonaws
- aws-java-sdk-kms
-
-
com.amazonaws
@@ -80,15 +70,15 @@
junit-jupiter-engine
test
+
+ org.mockito
+ mockito-core
+ test
+
- org.mockito
- mockito-core
- test
-
-
- junit
- junit
- test
+ org.hamcrest
+ hamcrest-core
+ test
diff --git a/serverless/src/test/java/com/iluwatar/serverless/baas/api/FindPersonApiHandlerTest.java b/serverless/src/test/java/com/iluwatar/serverless/baas/api/FindPersonApiHandlerTest.java
index 8eb209e06..003fdf0df 100644
--- a/serverless/src/test/java/com/iluwatar/serverless/baas/api/FindPersonApiHandlerTest.java
+++ b/serverless/src/test/java/com/iluwatar/serverless/baas/api/FindPersonApiHandlerTest.java
@@ -23,6 +23,9 @@
package com.iluwatar.serverless.baas.api;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -32,31 +35,29 @@ import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.iluwatar.serverless.baas.model.Person;
import java.util.Map;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
/**
* Unit tests for FindPersonApiHandler Created by dheeraj.mummar on 3/5/18.
*/
-@RunWith(MockitoJUnitRunner.class)
-public class FindPersonApiHandlerTest {
+class FindPersonApiHandlerTest {
private FindPersonApiHandler findPersonApiHandler;
@Mock
private DynamoDBMapper dynamoDbMapper;
- @Before
+ @BeforeEach
public void setUp() {
+ MockitoAnnotations.openMocks(this);
this.findPersonApiHandler = new FindPersonApiHandler();
this.findPersonApiHandler.setDynamoDbMapper(dynamoDbMapper);
}
@Test
- public void handleRequest() {
+ void handleRequest() {
findPersonApiHandler.handleRequest(apiGatewayProxyRequestEvent(), mock(Context.class));
verify(dynamoDbMapper, times(1)).load(Person.class, "37e7a1fe-3544-473d-b764-18128f02d72d");
}
diff --git a/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java b/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java
index bf4a59273..d9b12a71f 100644
--- a/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java
+++ b/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java
@@ -34,18 +34,18 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iluwatar.serverless.baas.model.Address;
import com.iluwatar.serverless.baas.model.Person;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.MockitoAnnotations;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* Unit tests for SavePersonApiHandler Created by dheeraj.mummar on 3/4/18.
*/
-@RunWith(MockitoJUnitRunner.class)
-public class SavePersonApiHandlerTest {
+class SavePersonApiHandlerTest {
private SavePersonApiHandler savePersonApiHandler;
@@ -54,31 +54,32 @@ public class SavePersonApiHandlerTest {
private final ObjectMapper objectMapper = new ObjectMapper();
- @Before
+ @BeforeEach
public void setUp() {
+ MockitoAnnotations.openMocks(this);
this.savePersonApiHandler = new SavePersonApiHandler();
this.savePersonApiHandler.setDynamoDbMapper(dynamoDbMapper);
}
@Test
- public void handleRequestSavePersonSuccessful() throws JsonProcessingException {
+ void handleRequestSavePersonSuccessful() throws JsonProcessingException {
var person = newPerson();
var body = objectMapper.writeValueAsString(person);
var request = apiGatewayProxyRequestEvent(body);
var ctx = mock(Context.class);
var apiGatewayProxyResponseEvent = this.savePersonApiHandler.handleRequest(request, ctx);
verify(dynamoDbMapper, times(1)).save(person);
- Assert.assertNotNull(apiGatewayProxyResponseEvent);
- Assert.assertEquals(Integer.valueOf(201), apiGatewayProxyResponseEvent.getStatusCode());
+ assertNotNull(apiGatewayProxyResponseEvent);
+ assertEquals(Integer.valueOf(201), apiGatewayProxyResponseEvent.getStatusCode());
}
@Test
- public void handleRequestSavePersonException() {
+ void handleRequestSavePersonException() {
var request = apiGatewayProxyRequestEvent("invalid sample request");
var ctx = mock(Context.class);
var apiGatewayProxyResponseEvent = this.savePersonApiHandler.handleRequest(request, ctx);
- Assert.assertNotNull(apiGatewayProxyResponseEvent);
- Assert.assertEquals(Integer.valueOf(400), apiGatewayProxyResponseEvent.getStatusCode());
+ assertNotNull(apiGatewayProxyResponseEvent);
+ assertEquals(Integer.valueOf(400), apiGatewayProxyResponseEvent.getStatusCode());
}
private APIGatewayProxyRequestEvent apiGatewayProxyRequestEvent(String body) {
From be72a96cd615e0864e1e39cb998689e1f7666191 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Wed, 29 Sep 2021 00:11:14 +0530
Subject: [PATCH 15/25] docs: add tan31989 as a contributor for code (#1820)
* docs: update README.md [skip ci]
* docs: update .all-contributorsrc [skip ci]
Co-authored-by: Subhrodip Mohanta
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
---
.all-contributorsrc | 9 +++++++++
README.md | 3 ++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 656110aae..95396655e 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -1586,6 +1586,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "tan31989",
+ "name": "Nagaraj Tantri",
+ "avatar_url": "https://avatars.githubusercontent.com/u/3784194?v=4",
+ "profile": "https://stackoverflow.com/users/308565/nagaraj-tantri",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 4,
diff --git a/README.md b/README.md
index 8e01ccfa1..1393b2e58 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-[](#contributors-)
+[](#contributors-)
@@ -336,6 +336,7 @@ This project is licensed under the terms of the MIT license.
 karthikbhat13 💻 |
 Morteza Adigozalpour 💻 |
+  Nagaraj Tantri 💻 |
From 42eb7950aef44e00b9a5363e681762a1bf1271c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?=
Date: Tue, 28 Sep 2021 21:43:31 +0300
Subject: [PATCH 16/25] task: Fix broken links (#1817)
* Fix some broken links
* Remove extra space
* Update filename
* Fix some links in localization folders
* Fix link
Co-authored-by: Subhrodip Mohanta
---
README.md | 2 +-
active-object/README.md | 2 +-
...{active-object.urm.PNG => active-object.urm.png} | Bin
command/README.md | 2 +-
commander/README.md | 2 +-
dependency-injection/README.md | 2 +-
event-driven-architecture/README.md | 3 +--
half-sync-half-async/README.md | 2 +-
localization/ko/strategy/README.md | 2 +-
localization/zh/active-object/README.md | 2 +-
localization/zh/chain/README.md | 2 +-
master-worker-pattern/README.md | 4 ++--
model-view-viewmodel/README.md | 2 +-
pipeline/README.md | 2 +-
subclass-sandbox/README.md | 2 +-
15 files changed, 15 insertions(+), 16 deletions(-)
rename active-object/etc/{active-object.urm.PNG => active-object.urm.png} (100%)
diff --git a/README.md b/README.md
index 1393b2e58..c7c137fa3 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
-Read in different language : [**zh**](/localization/zh/README.md), [**ko**](/localization/ko/README.md), [**fr**](/localization/fr/README.md), [**tr**](/localization/tr/README.md), [**ar**](/localization/ar/README.md), [**es**](/localization/es/README.md), [**pt**](/localization/pt/README.md)
+Read in different language : [**zh**](localization/zh/README.md), [**ko**](localization/ko/README.md), [**fr**](localization/fr/README.md), [**tr**](localization/tr/README.md), [**ar**](localization/ar/README.md), [**es**](localization/es/README.md), [**pt**](localization/pt/README.md)
diff --git a/active-object/README.md b/active-object/README.md
index 48db671e4..6e974034a 100644
--- a/active-object/README.md
+++ b/active-object/README.md
@@ -123,4 +123,4 @@ Now, we can create multiple creatures such as Orcs, tell them to eat and roam an
## Class diagram
-
+
diff --git a/active-object/etc/active-object.urm.PNG b/active-object/etc/active-object.urm.png
similarity index 100%
rename from active-object/etc/active-object.urm.PNG
rename to active-object/etc/active-object.urm.png
diff --git a/command/README.md b/command/README.md
index 03122c410..50e1ea47d 100644
--- a/command/README.md
+++ b/command/README.md
@@ -1,4 +1,4 @@
- ---
+---
layout: pattern
title: Command
folder: command
diff --git a/commander/README.md b/commander/README.md
index bff181698..f2469962b 100644
--- a/commander/README.md
+++ b/commander/README.md
@@ -25,4 +25,4 @@ We need a mechanism in place which can handle these kinds of situations. We have
## Credits
-* [https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/]
+* [Distributed Transactions: The Icebergs of Microservices](https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/)
diff --git a/dependency-injection/README.md b/dependency-injection/README.md
index 5a74c5d46..2959b7dac 100644
--- a/dependency-injection/README.md
+++ b/dependency-injection/README.md
@@ -103,4 +103,4 @@ Use the Dependency Injection pattern when:
* [Dependency Injection Principles, Practices, and Patterns](https://www.amazon.com/gp/product/161729473X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=161729473X&linkId=57079257a5c7d33755493802f3b884bd)
* [Clean Code: A Handbook of Agile Software Craftsmanship](https://www.amazon.com/gp/product/0132350882/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0132350882&linkCode=as2&tag=javadesignpat-20&linkId=2c390d89cc9e61c01b9e7005c7842871)
* [Java 9 Dependency Injection: Write loosely coupled code with Spring 5 and Guice](https://www.amazon.com/gp/product/1788296257/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=1788296257&linkId=4e9137a3bf722a8b5b156cce1eec0fc1)
-* [Google Guice Tutorial: Open source Java based dependency injection framework](https://www.amazon.com/gp/product/B083P7DZ8M/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B083P7DZ8M&linkId=04f0f902c877921e45215b624a124bfe)
+* [Google Guice: Agile Lightweight Dependency Injection Framework](https://www.amazon.com/gp/product/1590599977/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1590599977&linkId=3b10c90b7ba480a1b7777ff38000f956)
diff --git a/event-driven-architecture/README.md b/event-driven-architecture/README.md
index 3508fb631..295d23334 100644
--- a/event-driven-architecture/README.md
+++ b/event-driven-architecture/README.md
@@ -24,7 +24,6 @@ Use an Event-driven architecture when
## Real world examples
-* SendGrid, an email API, sends events whenever an email is processed, delivered, opened etc... (https://sendgrid.com/docs/API_Reference/Webhooks/event.html)
* Chargify, a billing API, exposes payment activity through various events (https://docs.chargify.com/api-events)
* Amazon's AWS Lambda, lets you execute code in response to events such as changes to Amazon S3 buckets, updates to an Amazon DynamoDB table, or custom events generated by your applications or devices. (https://aws.amazon.com/lambda)
* MySQL runs triggers based on events such as inserts and update events happening on database tables.
@@ -32,6 +31,6 @@ Use an Event-driven architecture when
## Credits
* [Event-driven architecture - Wikipedia](https://en.wikipedia.org/wiki/Event-driven_architecture)
-* [Fundamental Components of an Event-Driven Architecture](http://giocc.com/fundamental-components-of-an-event-driven-architecture.html)
+* [What is an Event-Driven Architecture](https://aws.amazon.com/event-driven-architecture/)
* [Real World Applications/Event Driven Applications](https://wiki.haskell.org/Real_World_Applications/Event_Driven_Applications)
* [Event-driven architecture definition](http://searchsoa.techtarget.com/definition/event-driven-architecture)
diff --git a/half-sync-half-async/README.md b/half-sync-half-async/README.md
index 76eb1e6cf..45dab2281 100644
--- a/half-sync-half-async/README.md
+++ b/half-sync-half-async/README.md
@@ -30,7 +30,7 @@ Use Half-Sync/Half-Async pattern when
* [BSD Unix networking subsystem](https://www.dre.vanderbilt.edu/~schmidt/PDF/PLoP-95.pdf)
* [Real Time CORBA](http://www.omg.org/news/meetings/workshops/presentations/realtime2001/4-3_Pyarali_thread-pool.pdf)
-* [Android AsyncTask framework](http://developer.android.com/reference/android/os/AsyncTask.html)
+* [Android AsyncTask framework](https://developer.android.com/reference/android/os/AsyncTask)
## Credits
diff --git a/localization/ko/strategy/README.md b/localization/ko/strategy/README.md
index cc13b3f36..ef45765dd 100644
--- a/localization/ko/strategy/README.md
+++ b/localization/ko/strategy/README.md
@@ -163,7 +163,7 @@ public class LambdaStrategy {
## 클래스 다이어그램
-
+
## 적용 가능성
diff --git a/localization/zh/active-object/README.md b/localization/zh/active-object/README.md
index ae1a1724f..301e27c1f 100644
--- a/localization/zh/active-object/README.md
+++ b/localization/zh/active-object/README.md
@@ -122,4 +122,4 @@ public class Orc extends ActiveCreature {
## 类图
-
+
diff --git a/localization/zh/chain/README.md b/localization/zh/chain/README.md
index 0262868a9..b4034d32c 100644
--- a/localization/zh/chain/README.md
+++ b/localization/zh/chain/README.md
@@ -139,7 +139,7 @@ king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); // Orc so
```
## 类图
-
+
## 适用性
使用责任链模式当
diff --git a/master-worker-pattern/README.md b/master-worker-pattern/README.md
index 8306accb6..005c3dc64 100644
--- a/master-worker-pattern/README.md
+++ b/master-worker-pattern/README.md
@@ -28,5 +28,5 @@ In this pattern, parallel processing is performed using a system consisting of a
## Credits
-* [https://docs.gigaspaces.com/sbp/master-worker-pattern.html]
-* [http://www.cs.sjsu.edu/~pearce/oom/patterns/behavioral/masterslave.htm]
+* [Master-Worker Pattern](https://docs.gigaspaces.com/sbp/master-worker-pattern.html)
+* [The Master-Slave Design Pattern](https://www.cs.sjsu.edu/~pearce/oom/patterns/behavioral/masterslave.htm)
diff --git a/model-view-viewmodel/README.md b/model-view-viewmodel/README.md
index 1306b7a0e..da961bf16 100644
--- a/model-view-viewmodel/README.md
+++ b/model-view-viewmodel/README.md
@@ -118,7 +118,7 @@ To deploy the example, go to model-view-viewmodel folder and run:
* [Zkoss Demo](https://www.zkoss.org/zkdemo/getting_started/mvvm)
* [Learn MVVM](https://www.learnmvvm.com/)
-* [Android Developer CodeLabs](https://codelabs.developers.google.com/codelabs/android-databinding)
+* [Data Binding in Android](https://developer.android.com/codelabs/android-databinding#0)
## Typical Use Case
diff --git a/pipeline/README.md b/pipeline/README.md
index a35c9941f..bd687f0c0 100644
--- a/pipeline/README.md
+++ b/pipeline/README.md
@@ -114,7 +114,7 @@ the [Single Responsibility Principle (SRP)](https://java-design-patterns.com/pri
## Related patterns
-* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain/)
+* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/)
## Credits
diff --git a/subclass-sandbox/README.md b/subclass-sandbox/README.md
index 40ec791a1..678a29c92 100644
--- a/subclass-sandbox/README.md
+++ b/subclass-sandbox/README.md
@@ -25,4 +25,4 @@ The Subclass Sandbox pattern is a very simple, common pattern lurking in lots of
## Credits
-* [Game Programming Patterns - Subclass Sandbox]([http://gameprogrammingpatterns.com/subclass-sandbox.html](http://gameprogrammingpatterns.com/subclass-sandbox.html))
+* [Game Programming Patterns - Subclass Sandbox](https://gameprogrammingpatterns.com/subclass-sandbox.html)
From 57f9c2e9687d959a9753297ab7641ded639947da Mon Sep 17 00:00:00 2001
From: Francesco Scuccimarri
Date: Tue, 5 Oct 2021 06:35:20 +0200
Subject: [PATCH 17/25] task: Update Lombok to version 1.18.20 (#1828)
Co-authored-by: Subhrodip Mohanta
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index cb726a43e..3e5e51abf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,7 +71,7 @@
1.1.0
2.0.0
3.5.0
- 1.18.14
+ 1.18.20
1.11.5
3.27.0-GA
3.0.0-M5
From 87cc4df14bd560736b2b588bb2456720f41a594d Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Tue, 5 Oct 2021 10:07:07 +0530
Subject: [PATCH 18/25] docs: Add frascu as a Contributor for Code (#1835)
* docs: update README.md [skip ci]
* docs: update .all-contributorsrc [skip ci]
Co-authored-by: Subhrodip Mohanta
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
---
.all-contributorsrc | 9 +++++++++
README.md | 3 ++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 95396655e..453fdfe3b 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -1595,6 +1595,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "frascu",
+ "name": "Francesco Scuccimarri",
+ "avatar_url": "https://avatars.githubusercontent.com/u/7107651?v=4",
+ "profile": "http://scuccimarri.it",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 4,
diff --git a/README.md b/README.md
index c7c137fa3..ae74f167b 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-[](#contributors-)
+[](#contributors-)
@@ -337,6 +337,7 @@ This project is licensed under the terms of the MIT license.
 karthikbhat13 💻 |
 Morteza Adigozalpour 💻 |
 Nagaraj Tantri 💻 |
+  Francesco Scuccimarri 💻 |
From c459b92933cc87fc406fe09f8f20907a84b7d7c6 Mon Sep 17 00:00:00 2001
From: Victor Zalevskii
Date: Wed, 6 Oct 2021 15:34:04 +0300
Subject: [PATCH 19/25] Fixes according PR comments. Mainly Readme edits.
---
caching/README.md | 123 ++++++++++--------
caching/etc/caching.png | Bin 114847 -> 0 bytes
.../main/java/com/iluwatar/caching/App.java | 25 +++-
.../java/com/iluwatar/caching/CacheStore.java | 2 +-
.../java/com/iluwatar/caching/LruCache.java | 2 +-
5 files changed, 88 insertions(+), 64 deletions(-)
delete mode 100644 caching/etc/caching.png
diff --git a/caching/README.md b/caching/README.md
index bd6e05f77..9c4d11b9e 100644
--- a/caching/README.md
+++ b/caching/README.md
@@ -43,39 +43,29 @@ Wikipedia says:
**Programmatic Example**
Let's first look at the data layer of our application. The interesting classes are `UserAccount`
-which is a simple Java object containing the user account details, and `DbManager` which handles
-reading and writing of these objects to/from MongoDB database.
+which is a simple Java object containing the user account details, and `DbManager` interface which handles
+reading and writing of these objects to/from database.
```java
-@Setter
-@Getter
+@Data
@AllArgsConstructor
@ToString
+@EqualsAndHashCode
public class UserAccount {
private String userId;
private String userName;
private String additionalInfo;
}
-@Slf4j
-public final class DbManager {
+public interface DbManager {
- private static MongoClient mongoClient;
- private static MongoDatabase db;
-
- private DbManager() { /*...*/ }
-
- public static void createVirtualDb() { /*...*/ }
-
- public static void connect() throws ParseException { /*...*/ }
-
- public static UserAccount readFromDb(String userId) { /*...*/ }
-
- public static void writeToDb(UserAccount userAccount) { /*...*/ }
-
- public static void updateDb(UserAccount userAccount) { /*...*/ }
-
- public static void upsertDb(UserAccount userAccount) { /*...*/ }
+ void connect();
+ void disconnect();
+
+ UserAccount readFromDb(String userId);
+ UserAccount writeToDb(UserAccount userAccount);
+ UserAccount updateDb(UserAccount userAccount);
+ UserAccount upsertDb(UserAccount userAccount);
}
```
@@ -171,30 +161,43 @@ strategies.
@Slf4j
public class CacheStore {
+ private static final int CAPACITY = 3;
private static LruCache cache;
+ private final DbManager dbManager;
/* ... details omitted ... */
- public static UserAccount readThrough(String userId) {
+ public UserAccount readThrough(final String userId) {
if (cache.contains(userId)) {
- LOGGER.info("# Cache Hit!");
+ LOGGER.info("# Found in Cache!");
return cache.get(userId);
}
- LOGGER.info("# Cache Miss!");
- UserAccount userAccount = DbManager.readFromDb(userId);
+ LOGGER.info("# Not found in cache! Go to DB!!");
+ UserAccount userAccount = dbManager.readFromDb(userId);
cache.set(userId, userAccount);
return userAccount;
}
- public static void writeThrough(UserAccount userAccount) {
+ public void writeThrough(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
- DbManager.updateDb(userAccount);
+ dbManager.updateDb(userAccount);
} else {
- DbManager.writeToDb(userAccount);
+ dbManager.writeToDb(userAccount);
}
cache.set(userAccount.getUserId(), userAccount);
}
+ public void writeAround(final UserAccount userAccount) {
+ if (cache.contains(userAccount.getUserId())) {
+ dbManager.updateDb(userAccount);
+ // Cache data has been updated -- remove older
+ cache.invalidate(userAccount.getUserId());
+ // version from cache.
+ } else {
+ dbManager.writeToDb(userAccount);
+ }
+ }
+
public static void clearCache() {
if (cache != null) {
cache.clear();
@@ -225,34 +228,39 @@ class.
public final class AppManager {
private static CachingPolicy cachingPolicy;
+ private final DbManager dbManager;
+ private final CacheStore cacheStore;
private AppManager() {
}
- public static void initDb(boolean useMongoDb) { /* ... */ }
+ public void initDb() { /* ... */ }
public static void initCachingPolicy(CachingPolicy policy) { /* ... */ }
public static void initCacheCapacity(int capacity) { /* ... */ }
- public static UserAccount find(String userId) {
- if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) {
- return CacheStore.readThrough(userId);
+ public UserAccount find(final String userId) {
+ LOGGER.info("Trying to find {} in cache", userId);
+ if (cachingPolicy == CachingPolicy.THROUGH
+ || cachingPolicy == CachingPolicy.AROUND) {
+ return cacheStore.readThrough(userId);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
- return CacheStore.readThroughWithWriteBackPolicy(userId);
+ return cacheStore.readThroughWithWriteBackPolicy(userId);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
return findAside(userId);
}
return null;
}
- public static void save(UserAccount userAccount) {
+ public void save(final UserAccount userAccount) {
+ LOGGER.info("Save record!");
if (cachingPolicy == CachingPolicy.THROUGH) {
- CacheStore.writeThrough(userAccount);
+ cacheStore.writeThrough(userAccount);
} else if (cachingPolicy == CachingPolicy.AROUND) {
- CacheStore.writeAround(userAccount);
+ cacheStore.writeAround(userAccount);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
- CacheStore.writeBehind(userAccount);
+ cacheStore.writeBehind(userAccount);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
saveAside(userAccount);
}
@@ -272,24 +280,35 @@ Here is what we do in the main class of the application.
@Slf4j
public class App {
- public static void main(String[] args) {
- AppManager.initDb(false);
- AppManager.initCacheCapacity(3);
- var app = new App();
+ public static void main(final String[] args) {
+ boolean isDbMongo = isDbMongo(args);
+ if(isDbMongo){
+ LOGGER.info("Using the Mongo database engine to run the application.");
+ } else {
+ LOGGER.info("Using the 'in Memory' database to run the application.");
+ }
+ App app = new App(isDbMongo);
app.useReadAndWriteThroughStrategy();
+ String splitLine = "==============================================";
+ LOGGER.info(splitLine);
app.useReadThroughAndWriteAroundStrategy();
+ LOGGER.info(splitLine);
app.useReadThroughAndWriteBehindStrategy();
+ LOGGER.info(splitLine);
app.useCacheAsideStategy();
+ LOGGER.info(splitLine);
}
public void useReadAndWriteThroughStrategy() {
LOGGER.info("# CachingPolicy.THROUGH");
- AppManager.initCachingPolicy(CachingPolicy.THROUGH);
+ appManager.initCachingPolicy(CachingPolicy.THROUGH);
+
var userAccount1 = new UserAccount("001", "John", "He is a boy.");
- AppManager.save(userAccount1);
- LOGGER.info(AppManager.printCacheContent());
- AppManager.find("001");
- AppManager.find("001");
+
+ appManager.save(userAccount1);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("001");
+ appManager.find("001");
}
public void useReadThroughAndWriteAroundStrategy() { /* ... */ }
@@ -300,16 +319,6 @@ public class App {
}
```
-Finally, here is some of the console output from the program.
-
-```
-12:32:53.845 [main] INFO com.iluwatar.caching.App - # CachingPolicy.THROUGH
-12:32:53.900 [main] INFO com.iluwatar.caching.App -
---CACHE CONTENT--
-UserAccount(userId=001, userName=John, additionalInfo=He is a boy.)
-----
-```
-
## Class diagram

diff --git a/caching/etc/caching.png b/caching/etc/caching.png
deleted file mode 100644
index b6ed703ab8b7dfce5e5d82f097dac534828fc93a..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 114847
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