From 399ad53b09fec5aa6a23ecf04bf40da56c0d72e9 Mon Sep 17 00:00:00 2001 From: Victor Zalevskii Date: Tue, 17 Aug 2021 16:25:59 +0300 Subject: [PATCH 01/25] Changed database implementation. Removed static objects. --- .../main/java/com/iluwatar/caching/App.java | 205 ++++++++++-------- .../java/com/iluwatar/caching/AppManager.java | 65 +++--- .../java/com/iluwatar/caching/CacheStore.java | 50 +++-- .../java/com/iluwatar/caching/DbManager.java | 171 --------------- .../iluwatar/caching/database/DbManager.java | 16 ++ .../caching/database/DbManagerFactory.java | 10 + .../iluwatar/caching/database/MongoDb.java | 81 +++++++ .../iluwatar/caching/database/VirtualDb.java | 44 ++++ .../com/iluwatar/caching/CachingTest.java | 4 +- 9 files changed, 326 insertions(+), 320 deletions(-) delete mode 100644 caching/src/main/java/com/iluwatar/caching/DbManager.java create mode 100644 caching/src/main/java/com/iluwatar/caching/database/DbManager.java create mode 100644 caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java create mode 100644 caching/src/main/java/com/iluwatar/caching/database/MongoDb.java create mode 100644 caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java index f5df323aa..bddc1f1c6 100644 --- a/caching/src/main/java/com/iluwatar/caching/App.java +++ b/caching/src/main/java/com/iluwatar/caching/App.java @@ -23,6 +23,8 @@ package com.iluwatar.caching; +import com.iluwatar.caching.database.DbManager; +import com.iluwatar.caching.database.DbManagerFactory; import lombok.extern.slf4j.Slf4j; /** @@ -55,111 +57,138 @@ import lombok.extern.slf4j.Slf4j; * {@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> DBManager} *

* + *

+ * To run the application with MongoDb, just start it with parameter --mongo + * Example: java -jar app.jar --mongo + *

+ * * @see CacheStore * @see LruCache * @see CachingPolicy */ @Slf4j public class App { + private static final String USE_MONGO_DB = "--mongo"; + private DbManager dbManager; + private AppManager appManager; - /** - * Program entry point. - * - * @param args command line args - */ - public static void main(String[] args) { - AppManager.initDb(false); // 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). - AppManager.initCacheCapacity(3); - var app = new App(); - app.useReadAndWriteThroughStrategy(); - app.useReadThroughAndWriteAroundStrategy(); - app.useReadThroughAndWriteBehindStrategy(); - app.useCacheAsideStategy(); - } + public App(boolean isMongo) { + dbManager = DbManagerFactory.initDb(isMongo); + appManager = new AppManager(dbManager); + appManager.initDb(); + } - /** - * Read-through and write-through. - */ - public void useReadAndWriteThroughStrategy() { - LOGGER.info("# CachingPolicy.THROUGH"); - AppManager.initCachingPolicy(CachingPolicy.THROUGH); + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(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(); + app.useReadThroughAndWriteAroundStrategy(); + app.useReadThroughAndWriteBehindStrategy(); + app.useCacheAsideStategy(); + } - var userAccount1 = new UserAccount("001", "John", "He is a boy."); + /** + * Check the input parameters. if + * @param args input params + * @return true if there is "--mongo" parameter in arguments + */ + private static boolean isDbMongo(String[] args) { + for (String arg : args) { + if (arg.equals(USE_MONGO_DB)) { + return true; + } + } + return false; + } - AppManager.save(userAccount1); - LOGGER.info(AppManager.printCacheContent()); - AppManager.find("001"); - AppManager.find("001"); - } + /** + * Read-through and write-through. + */ + public void useReadAndWriteThroughStrategy() { + LOGGER.info("# CachingPolicy.THROUGH"); + appManager.initCachingPolicy(CachingPolicy.THROUGH); - /** - * Read-through and write-around. - */ - public void useReadThroughAndWriteAroundStrategy() { - LOGGER.info("# CachingPolicy.AROUND"); - AppManager.initCachingPolicy(CachingPolicy.AROUND); + var userAccount1 = new UserAccount("001", "John", "He is a boy."); - var userAccount2 = new UserAccount("002", "Jane", "She is a girl."); + appManager.save(userAccount1); + LOGGER.info(appManager.printCacheContent()); + appManager.find("001"); + appManager.find("001"); + } - 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"); - } + /** + * Read-through and write-around. + */ + public void useReadThroughAndWriteAroundStrategy() { + LOGGER.info("# CachingPolicy.AROUND"); + appManager.initCachingPolicy(CachingPolicy.AROUND); - /** - * Read-through and write-behind. - */ - public void useReadThroughAndWriteBehindStrategy() { - LOGGER.info("# CachingPolicy.BEHIND"); - AppManager.initCachingPolicy(CachingPolicy.BEHIND); + var userAccount2 = new UserAccount("002", "Jane", "She is a girl."); - 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(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"); + } - 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()); - } + /** + * Read-through and write-behind. + */ + public void useReadThroughAndWriteBehindStrategy() { + LOGGER.info("# CachingPolicy.BEHIND"); + appManager.initCachingPolicy(CachingPolicy.BEHIND); - /** - * 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."); - 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); + 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()); + } - LOGGER.info(AppManager.printCacheContent()); - AppManager.find("003"); - LOGGER.info(AppManager.printCacheContent()); - AppManager.find("004"); - LOGGER.info(AppManager.printCacheContent()); - } + /** + * 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()); + } } diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java index 68c8a0d36..cca24014a 100644 --- a/caching/src/main/java/com/iluwatar/caching/AppManager.java +++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java @@ -23,8 +23,9 @@ package com.iluwatar.caching; -import java.text.ParseException; import java.util.Optional; + +import com.iluwatar.caching.database.DbManager; import lombok.extern.slf4j.Slf4j; /** @@ -35,11 +36,15 @@ import lombok.extern.slf4j.Slf4j; * CacheStore class. */ @Slf4j -public final class AppManager { +public class AppManager { private static CachingPolicy cachingPolicy; + private final DbManager dbManager; + private final CacheStore cacheStore; - private AppManager() { + public AppManager(DbManager dbManager) { + this.dbManager = dbManager; + this.cacheStore = new CacheStore(dbManager); } /** @@ -47,41 +52,29 @@ public final class AppManager { * data storage or a simple Java data structure to (temporarily) store the data/objects during * runtime. */ - public static void initDb(boolean useMongoDb) { - if (useMongoDb) { - try { - DbManager.connect(); - } catch (ParseException e) { - LOGGER.error("Error connecting to MongoDB", e); - } - } else { - DbManager.createVirtualDb(); - } + public void initDb() { + dbManager.connect(); } /** * Initialize caching policy. */ - public static void initCachingPolicy(CachingPolicy policy) { + public void initCachingPolicy(CachingPolicy policy) { cachingPolicy = policy; if (cachingPolicy == CachingPolicy.BEHIND) { - Runtime.getRuntime().addShutdownHook(new Thread(CacheStore::flushCache)); + Runtime.getRuntime().addShutdownHook(new Thread(cacheStore::flushCache)); } - CacheStore.clearCache(); - } - - public static void initCacheCapacity(int capacity) { - CacheStore.initCapacity(capacity); + cacheStore.clearCache(); } /** * Find user account. */ - public static UserAccount find(String userId) { + public UserAccount find(String userId) { if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) { - return CacheStore.readThrough(userId); + 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); } @@ -91,38 +84,38 @@ public final class AppManager { /** * Save user account. */ - public static void save(UserAccount userAccount) { + public void save(UserAccount userAccount) { 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); } } - public static String printCacheContent() { - return CacheStore.print(); + public String printCacheContent() { + return cacheStore.print(); } /** * Cache-Aside save user account helper. */ - private static void saveAside(UserAccount userAccount) { - DbManager.updateDb(userAccount); - CacheStore.invalidate(userAccount.getUserId()); + private void saveAside(UserAccount userAccount) { + dbManager.updateDb(userAccount); + cacheStore.invalidate(userAccount.getUserId()); } /** * Cache-Aside find user account helper. */ - private static UserAccount findAside(String userId) { - return Optional.ofNullable(CacheStore.get(userId)) + private UserAccount findAside(String userId) { + return Optional.ofNullable(cacheStore.get(userId)) .or(() -> { - Optional userAccount = Optional.ofNullable(DbManager.readFromDb(userId)); - userAccount.ifPresent(account -> CacheStore.set(userId, account)); + 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 156f024d4..9d3bef747 100644 --- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java +++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java @@ -26,6 +26,8 @@ package com.iluwatar.caching; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; + +import com.iluwatar.caching.database.DbManager; import lombok.extern.slf4j.Slf4j; /** @@ -33,16 +35,20 @@ import lombok.extern.slf4j.Slf4j; */ @Slf4j public class CacheStore { + private static final int CAPACITY = 3; private static LruCache cache; + private DbManager dbManager; - private CacheStore() { + public CacheStore(DbManager dbManager) { + this.dbManager = dbManager; + initCapacity(CAPACITY); } /** * Init cache capacity. */ - public static void initCapacity(int capacity) { + public void initCapacity(int capacity) { if (cache == null) { cache = new LruCache(capacity); } else { @@ -53,13 +59,13 @@ public class CacheStore { /** * Get user account using read-through cache. */ - public static UserAccount readThrough(String userId) { + public UserAccount readThrough(String userId) { if (cache.contains(userId)) { LOGGER.info("# Cache Hit!"); return cache.get(userId); } LOGGER.info("# Cache Miss!"); - UserAccount userAccount = DbManager.readFromDb(userId); + UserAccount userAccount = dbManager.readFromDb(userId); cache.set(userId, userAccount); return userAccount; } @@ -67,11 +73,11 @@ public class CacheStore { /** * Get user account using write-through cache. */ - public static void writeThrough(UserAccount userAccount) { + public void writeThrough(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); } @@ -79,30 +85,30 @@ public class CacheStore { /** * Get user account using write-around cache. */ - public static void writeAround(UserAccount userAccount) { + public void writeAround(UserAccount userAccount) { if (cache.contains(userAccount.getUserId())) { - DbManager.updateDb(userAccount); + dbManager.updateDb(userAccount); cache.invalidate(userAccount.getUserId()); // Cache data has been updated -- remove older // version from cache. } else { - DbManager.writeToDb(userAccount); + dbManager.writeToDb(userAccount); } } /** * Get user account using read-through cache with write-back policy. */ - public static UserAccount readThroughWithWriteBackPolicy(String userId) { + public UserAccount readThroughWithWriteBackPolicy(String userId) { if (cache.contains(userId)) { LOGGER.info("# Cache Hit!"); return cache.get(userId); } LOGGER.info("# Cache Miss!"); - UserAccount userAccount = DbManager.readFromDb(userId); + UserAccount userAccount = dbManager.readFromDb(userId); if (cache.isFull()) { LOGGER.info("# Cache is FULL! Writing LRU data to DB..."); UserAccount toBeWrittenToDb = cache.getLruData(); - DbManager.upsertDb(toBeWrittenToDb); + dbManager.upsertDb(toBeWrittenToDb); } cache.set(userId, userAccount); return userAccount; @@ -111,11 +117,11 @@ public class CacheStore { /** * Set user account. */ - public static void writeBehind(UserAccount userAccount) { + public void writeBehind(UserAccount userAccount) { if (cache.isFull() && !cache.contains(userAccount.getUserId())) { LOGGER.info("# Cache is FULL! Writing LRU data to DB..."); UserAccount toBeWrittenToDb = cache.getLruData(); - DbManager.upsertDb(toBeWrittenToDb); + dbManager.upsertDb(toBeWrittenToDb); } cache.set(userAccount.getUserId(), userAccount); } @@ -123,7 +129,7 @@ public class CacheStore { /** * Clears cache. */ - public static void clearCache() { + public void clearCache() { if (cache != null) { cache.clear(); } @@ -132,18 +138,18 @@ public class CacheStore { /** * Writes remaining content in the cache into the DB. */ - public static void flushCache() { + public void flushCache() { LOGGER.info("# flushCache..."); Optional.ofNullable(cache) .map(LruCache::getCacheDataInListForm) .orElse(List.of()) - .forEach(DbManager::updateDb); + .forEach(dbManager::updateDb); } /** * Print user accounts. */ - public static String print() { + public String print() { return Optional.ofNullable(cache) .map(LruCache::getCacheDataInListForm) .orElse(List.of()) @@ -155,21 +161,21 @@ public class CacheStore { /** * Delegate to backing cache store. */ - public static UserAccount get(String userId) { + public UserAccount get(String userId) { return cache.get(userId); } /** * Delegate to backing cache store. */ - public static void set(String userId, UserAccount userAccount) { + public void set(String userId, UserAccount userAccount) { cache.set(userId, userAccount); } /** * Delegate to backing cache store. */ - public static void invalidate(String userId) { + public void invalidate(String userId) { cache.invalidate(userId); } } diff --git a/caching/src/main/java/com/iluwatar/caching/DbManager.java b/caching/src/main/java/com/iluwatar/caching/DbManager.java deleted file mode 100644 index d02234969..000000000 --- a/caching/src/main/java/com/iluwatar/caching/DbManager.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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.constants.CachingConstants; -import com.mongodb.MongoClient; -import com.mongodb.client.MongoDatabase; -import com.mongodb.client.model.UpdateOptions; -import java.text.ParseException; -import java.util.HashMap; -import java.util.Map; -import lombok.extern.slf4j.Slf4j; -import org.bson.Document; - -/** - *

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.

- * - *

Developer/Tester is able to choose whether the application should use MongoDB as its - * underlying data storage (connect()) or a simple Java data structure to (temporarily) store the - * data/objects during runtime (createVirtualDB()).

- */ -@Slf4j -public final class DbManager { - - private static MongoClient mongoClient; - private static MongoDatabase db; - private static boolean useMongoDB; - - private static Map virtualDB; - - private DbManager() { - } - - /** - * Create DB. - */ - public static void createVirtualDb() { - useMongoDB = false; - virtualDB = new HashMap<>(); - } - - /** - * Connect to DB. - */ - public static void connect() throws ParseException { - useMongoDB = true; - mongoClient = new MongoClient(); - db = mongoClient.getDatabase("test"); - } - - /** - * Read user account from DB. - */ - public static UserAccount readFromDb(String userId) { - if (!useMongoDB) { - if (virtualDB.containsKey(userId)) { - return virtualDB.get(userId); - } - return null; - } - if (db == null) { - try { - connect(); - } catch (ParseException e) { - LOGGER.error("Error connecting to MongoDB", e); - } - } - var iterable = db - .getCollection(CachingConstants.USER_ACCOUNT) - .find(new Document(CachingConstants.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); - return new UserAccount(userId, userName, appInfo); - } - - /** - * Write user account to DB. - */ - public static void writeToDb(UserAccount userAccount) { - if (!useMongoDB) { - virtualDB.put(userAccount.getUserId(), userAccount); - return; - } - if (db == null) { - try { - connect(); - } catch (ParseException e) { - LOGGER.error("Error connecting to MongoDB", e); - } - } - 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()) - ); - } - - /** - * Update DB. - */ - public static void updateDb(UserAccount userAccount) { - if (!useMongoDB) { - virtualDB.put(userAccount.getUserId(), userAccount); - return; - } - if (db == null) { - try { - connect(); - } catch (ParseException e) { - LOGGER.error("Error connecting to MongoDB", e); - } - } - 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()))); - } - - /** - * Insert data into DB if it does not exist. Else, update it. - */ - public static void upsertDb(UserAccount userAccount) { - if (!useMongoDB) { - virtualDB.put(userAccount.getUserId(), userAccount); - return; - } - if (db == null) { - try { - connect(); - } catch (ParseException e) { - LOGGER.error("Error connecting to MongoDB", e); - } - } - db.getCollection(CachingConstants.USER_ACCOUNT).updateOne( - new Document(CachingConstants.USER_ID, userAccount.getUserId()), - new Document("$set", - new Document(CachingConstants.USER_ID, userAccount.getUserId()) - .append(CachingConstants.USER_NAME, userAccount.getUserName()) - .append(CachingConstants.ADD_INFO, userAccount.getAdditionalInfo()) - ), - new UpdateOptions().upsert(true) - ); - } -} diff --git a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java new file mode 100644 index 000000000..d1ec2dd3f --- /dev/null +++ b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java @@ -0,0 +1,16 @@ +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.

+ */ +public interface DbManager { + void connect(); + UserAccount readFromDb(String userId); + UserAccount writeToDb(UserAccount userAccount); + UserAccount updateDb(UserAccount 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 new file mode 100644 index 000000000..0e6ed9fe1 --- /dev/null +++ b/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java @@ -0,0 +1,10 @@ +package com.iluwatar.caching.database; + +public class DbManagerFactory { + public static DbManager initDb(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 new file mode 100644 index 000000000..50a5d805d --- /dev/null +++ b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java @@ -0,0 +1,81 @@ +package com.iluwatar.caching.database; + +import com.iluwatar.caching.UserAccount; +import com.iluwatar.caching.constants.CachingConstants; +import com.mongodb.MongoClient; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.UpdateOptions; +import org.bson.Document; + +/** + * Implementation of DatabaseManager. + * implements base methods to work with MongoDb. + */ +public class MongoDb implements DbManager { + private MongoDatabase db; + + @Override + public void connect() { + MongoClient mongoClient = new MongoClient(); + db = mongoClient.getDatabase("test"); + } + + @Override + public UserAccount readFromDb(String userId) { + if (db == null) { + connect(); + } + var iterable = db + .getCollection(CachingConstants.USER_ACCOUNT) + .find(new Document(CachingConstants.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); + return new UserAccount(userId, userName, appInfo); + } + + @Override + public UserAccount writeToDb(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()) + ); + return userAccount; + } + + @Override + public UserAccount updateDb(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()))); + return userAccount; + } + + @Override + public UserAccount upsertDb(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_ID, userAccount.getUserId()) + .append(CachingConstants.USER_NAME, userAccount.getUserName()) + .append(CachingConstants.ADD_INFO, userAccount.getAdditionalInfo()) + ), + 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 new file mode 100644 index 000000000..af67225ad --- /dev/null +++ b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java @@ -0,0 +1,44 @@ +package com.iluwatar.caching.database; + +import com.iluwatar.caching.UserAccount; + +import java.util.HashMap; +import java.util.Map; + +/** + * Implementation of DatabaseManager. + * implements base methods to work with hashMap as database. + */ +public class VirtualDb implements DbManager { + private Map virtualDB; + + @Override + public void connect() { + virtualDB = new HashMap<>(); + } + + @Override + public UserAccount readFromDb(String userId) { + if (virtualDB.containsKey(userId)) { + return virtualDB.get(userId); + } + return null; + } + + @Override + public UserAccount writeToDb(UserAccount userAccount) { + virtualDB.put(userAccount.getUserId(), userAccount); + return userAccount; + } + + @Override + public UserAccount updateDb(UserAccount userAccount) { + virtualDB.put(userAccount.getUserId(), userAccount); + return userAccount; + } + + @Override + public UserAccount upsertDb(UserAccount userAccount) { + return updateDb(userAccount); + } +} \ No newline at end of file diff --git a/caching/src/test/java/com/iluwatar/caching/CachingTest.java b/caching/src/test/java/com/iluwatar/caching/CachingTest.java index 8869a66ac..8a02869bc 100644 --- a/caching/src/test/java/com/iluwatar/caching/CachingTest.java +++ b/caching/src/test/java/com/iluwatar/caching/CachingTest.java @@ -43,9 +43,7 @@ class CachingTest { // 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). - AppManager.initDb(false); - AppManager.initCacheCapacity(3); - app = new App(); + app = new App(false); } @Test From a437f5b2eb8e74e41b8a9a4213e05abde5da9ba5 Mon Sep 17 00:00:00 2001 From: Victor Zalevskii Date: Tue, 17 Aug 2021 16:53:44 +0300 Subject: [PATCH 02/25] Fix Logs --- caching/src/main/java/com/iluwatar/caching/App.java | 7 +++++-- .../src/main/java/com/iluwatar/caching/AppManager.java | 2 ++ .../src/main/java/com/iluwatar/caching/CacheStore.java | 10 +++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java index bddc1f1c6..14f16f365 100644 --- a/caching/src/main/java/com/iluwatar/caching/App.java +++ b/caching/src/main/java/com/iluwatar/caching/App.java @@ -69,11 +69,10 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class App { private static final String USE_MONGO_DB = "--mongo"; - private DbManager dbManager; private AppManager appManager; public App(boolean isMongo) { - dbManager = DbManagerFactory.initDb(isMongo); + DbManager dbManager = DbManagerFactory.initDb(isMongo); appManager = new AppManager(dbManager); appManager.initDb(); } @@ -90,9 +89,13 @@ public class App { // installed and socket connection is open). App app = new App(isDbMongo(args)); app.useReadAndWriteThroughStrategy(); + System.out.println("====================================================="); app.useReadThroughAndWriteAroundStrategy(); + System.out.println("====================================================="); app.useReadThroughAndWriteBehindStrategy(); + System.out.println("====================================================="); app.useCacheAsideStategy(); + System.out.println("====================================================="); } /** diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java index cca24014a..880791034 100644 --- a/caching/src/main/java/com/iluwatar/caching/AppManager.java +++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java @@ -71,6 +71,7 @@ public class AppManager { * Find user account. */ public UserAccount find(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) { @@ -85,6 +86,7 @@ public class AppManager { * Save user account. */ public void save(UserAccount userAccount) { + LOGGER.info("Save record!"); if (cachingPolicy == CachingPolicy.THROUGH) { cacheStore.writeThrough(userAccount); } else if (cachingPolicy == CachingPolicy.AROUND) { diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java index 9d3bef747..3e229d580 100644 --- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java +++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java @@ -61,10 +61,10 @@ public class CacheStore { */ public UserAccount readThrough(String userId) { if (cache.contains(userId)) { - LOGGER.info("# Cache Hit!"); + LOGGER.info("# Found in Cache!"); return cache.get(userId); } - LOGGER.info("# Cache Miss!"); + LOGGER.info("# Not found in cache! Go to DB!!"); UserAccount userAccount = dbManager.readFromDb(userId); cache.set(userId, userAccount); return userAccount; @@ -100,10 +100,10 @@ public class CacheStore { */ public UserAccount readThroughWithWriteBackPolicy(String userId) { if (cache.contains(userId)) { - LOGGER.info("# Cache Hit!"); + LOGGER.info("# Found in cache!"); return cache.get(userId); } - LOGGER.info("# Cache Miss!"); + LOGGER.info("# Not found in Cache!"); UserAccount userAccount = dbManager.readFromDb(userId); if (cache.isFull()) { LOGGER.info("# Cache is FULL! Writing LRU data to DB..."); @@ -155,7 +155,7 @@ public class CacheStore { .orElse(List.of()) .stream() .map(userAccount -> userAccount.toString() + "\n") - .collect(Collectors.joining("", "\n--CACHE CONTENT--\n", "----\n")); + .collect(Collectors.joining("", "\n--CACHE CONTENT--\n", "----")); } /** From 3c213fcd6975f5b507627381892fb4db621d84d2 Mon Sep 17 00:00:00 2001 From: Victor Z 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 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![All Contributors](https://img.shields.io/badge/all_contributors-174-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-175-orange.svg?style=flat-square)](#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 -![alt text](./etc/active-object.urm.PNG "Active Object class diagram") +![alt text](./etc/active-object.urm.png "Active Object 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 { ## 클래스 다이어그램 -![alt text](./etc/strategy_urm.png "Strategy") +![alt text](../../../strategy/etc/strategy_urm.png "Strategy") ## 적용 가능성 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 { ## 类图 -![alt text](../../../active-object/etc/active-object.urm.PNG "Active Object class diagram") +![alt text](../../../active-object/etc/active-object.urm.png "Active Object class diagram") 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 ``` ## 类图 -![alt text](../../../chain/etc/chain.urm.png "Chain of Responsibility class diagram") +![alt text](../../../chain-of-responsibility/etc/chain-of-responsibility.urm.png "Chain of Responsibility class diagram") ## 适用性 使用责任链模式当 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 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![All Contributors](https://img.shields.io/badge/all_contributors-175-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-176-orange.svg?style=flat-square)](#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 ![alt text](./etc/caching.png "Caching") 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 zcmb5WbySsIyY`K-5SMgHNK1#(4U0wu=|&oqM!IFuASs}9hjfE$x5I=37`lF2q;pLq6!EIk5v&69$Fwh1Rrrj zLDEEc&}1wn3R7}Q*iC%uhT2KkDe{4?3jG{|pe?$3n<{`}p&YXa6l1$Ho4u2B`(d#kxT^kK}V!Ufx>p-(cUd zyQ9g3c#4qMLkJX`M(*$bm_a$#nGpW#@8BQ4v!O%e|LwDyYaYs@|JP@*`V(=R)5vvg ze>UyNOF&T;fMUPJZL5?>ghHUu=93O#@a2yYpEr=YiI5xpU>o1Hh)(a3Adh{kKbtP= zE2#*lFXAm}8YyNsA32mf;spEtn@r&5w35dPiNYu&okg)l$+u|JNTJyI zjB@@)<-~kkx@RnEK>_g6=$viMaPVhl7h;*PC=8oMO|$Fl#YDZU&MMJiygIMj&84W5 zQ{$(#H&Kk5L~U?X0r8Tk*WpEJ*miH7|Ft@Ss0_vd)zg<1qW(*GCVx{rntoSkehE_iNc{_+-iE zMT4>A<&1-J1*AMNAV$96i8BB7)dF8kwE9cQ%eVGK(Gox3bX|J`7f$SC;rXkSM01YAR0|8++naNNq))3IN%gMjK903yTf_>A zikBxJ_f}Tq(nOoPx?rWWG#t=_Ep=pS_@ff-ZYqt0062?;(y5eR5Dks37{~VzF>FyU zcmfELdoV=>gUF_74Hys?^)u*Eu{u{D=c)mm$(|I{Y+^oeVEg_l~u5R-l3X`e}z%6o#t%9GhAfPKS$p`k>efs z5evNKakNZN!B-)`VgF)}e|~V_=uqi-LbJ6sYN)E(S?h>pRA!TU@cZm6ijnZZ&+xq1 zVix&Chu3ky<@OqhGQPzZt9ffFV`Y^h3X4%p6o!wZ?Eb9a(ijiCl|b`Xv$LfCY%GSt z)|oOB*_%s&%r`jitF7C;$l*V*LJN`Mn)zuFjlUi?#U$v>=@O-7fNhSe*)94xv)Q2aGS5O zh8@dRm~&*|F)JMXD&hS^8JFGGDS=HJc?Xtn0i}EY0sj7ijOgp3FefMM&`@Fu8Ey|Z z2sBI){XmCfctsRD>SJ2&I~GY%SS(X%^Df=LUX`7{{q`8o#Edl7@J}{VrM<&d5qi~7 zdc`cRk&OWqi`h!Als{z*uZF(86z)LEu8(X6B;Mb(wp}y z2q*bzn&2vUYc8iwf{g^sI>aLS!MGJeTat=$=GgCdtV;uNslgKAg438p4(%b2Krt4? z00l_bf~?j-&GCS$(p;VT9WgH+-V=_%f&O^cTxI%=y*>4YItfwHfOG@m+aqB)xiZuj z1SQqN9EGn9Ts(fm5XcM{yxp~8orM~)65zZfizuq z;D^Tyrs+B%CoGry*LQVwR0ErE@^Y*>kvH(t?OG_=d^a8iac_CLtW{$)f#gbI$^5}tv#2AnWNtYm+fQJyXXaE1ooEVE&09=ytIDjTV zbOcnXTomq4Q*y=YpP}Jm%DsL(3zreLNYvAfWr6xh+EfOd$dpQ$BoK)WPT_ zSoxGLg?x$o?>L>WOomUFmzhFai<`3ZRGox}N{<0+WjwnaR--Zib{gqg)E9dCvyUj` zP=#+NRrE~Yz|*CreCeu-X>oYDFbis^I`m47j@DKTJ7Xq1Q3EFuqf{0uETL$j5Zql$ zWiD91)>Y&ms>2<$_#KO$YA@~=cTK7aXIsYrzo&-}lN4tXe9VqKp`}X$gd#O=y zeElnEsW9~N>?Dw9bk;NG)%sHVjfRncHJQzIs0~;HjK=S}522=@Knx^&^xv8&h@CD9 zQ)>_UnXY7do1H`>$;dKR_+3f!RB9`Tri&|fUg0@pdS<=tpw78fO+S$*S`8{MrxHLT zSwV)SiK8}gwx@G_;F%YVRR)J!E-sjzy z9-hA+N}k(s|C^YAmeuP1xN*Z^*R#H6eZya*ZSt?$Tu**4mVY#D56V1Rv+YQ-3H^wd z>uQUQOs)AtVXLj}k8z{TmWnPoQay6Dn0Bf+)bAqYX|K+$2w(7bjFzb#u0V_ji6i&v zA_K*+4HNn-X}`eV#4!9?U!N;AdI1|{ec+X=!bqQ$a?lv|6l$tcoI+R$@qVo;p4vK8 z@s!>(p5t_waXfG1=5+G7#9uyLKtJ!~fR<6?e#gg-WCQ3WU%mHJa26%Ra}Mkc z)pD!F@C_gOzywB(u^Kn$_#!Lii*L3Yex(WB-O_dS{CbUC7!wx>0xxE39T7CbfK+KB ziN;0iM|z`NX?~b4kzVqyfn@hVM`*_b3wWXMiUt{>2BQ|gmPloRd0LN!V#N!U%s8olSn}wJ8>JpivH+kB%+4@!R{HozW~30VVxO#tz4>!Q?BMWom>xkg92F4@n-^I- zUWf((cUd)6q74J%3ByywvnX7~G}3WD&wzOG~swBpAPlwgvs%;N%J3aC-D8m zwHcBHr^8dJHBO_du=7*PQ_Tjqd~*&aW-kY|3Z1v-LHx2$h<1-??9HhQ>o1`X#b zYg1-rFnC*(qJ$9z7Aj;S_XeIlu;@K#^bEvybW~NF`=PiaMD>BFNwxTVYRY<-nyY&J z_wsykcOVx1aIug7`D}$5c(wbBi(v}OJfAD32yL34o0wd&n+Y_!*i-i<8+~P@#B#Vg z?A)_Kv3ZHlvh_x(z#_M@HCG^0COWV`9X0C)%kku>Y5^-w`L>SMNAk3r(MxoK=-NnYG z;dDw4b>Q!5w zGJSSkkfA;X1fRN06PisrF-QH95q{^@HBm6i4QUQk2a+dCMiJxg>cE|$F=a|`bLO!n z`c8=&brA8hVC`BC9OT`99aMaCqtJMHFjYliIx2RS6`1yGNNPj`{Z zKbxX8_K%Mr?$3)(n``xr+}xN|nN5D)8hyw6l%T0;o#EZ5qsSl}>8CZcNTC$cM6c6a zL1|DY+Cx?mlL-3(c47~Lc1?LZQ0OVCq~U12RGNIZ;zT5PY>t*6=4+y-six2-T1=uD zDe)ZOW&!m0BqXT`IkK}#7XIUWxvKT}dmWU0LS%P!p8bx-{(h`8B0CK-Q=d##hkD2u z)8K7)ZEFLGXDZL>C^aAN6?ZZOcyms9>LFFTf2Bqf8pmNP$|TEFDF|vZ4hSjyvs09q zZ{cWTv&7r!ZsO^aDlDAC$YJH*xPm*|Ri9BnpLmmqhyL3y(`$kwj?A`5CwV9><#dPo zqda=i;Fq1tbE{t5K)B>3sHh4#f6tns)l8H5(w`EZ+~R+KCDkkeU+_ndz0rbV7}Sx7 z1FOxvY_SLlQ9saXagmG!uaXDj=4Zxag5-_|Sycyk{&N;h0t%KZHF z(#Mf@^Y?OQ0-TROxI~mFps_&Xhr&lTjAYM+CugA|w#~v30}eTPP6|*c%3eFtk&-EQ4K3D6!`>ap`lSO>+PX& zzv-u&%N>=8$}+*O?=C05t0Eqsl{VGbe@uk9(kd}%Z-I$OjK`r1x7k3b;(VdkcczOb zM>68te0+O+wSuN=em=Tjrs3vU8foNfpb4;Z1ss>u`bAE~lq<`6Lnc)8qNj^KR{#}y zFA}&QdeNZPQQ#7Zy@hRVd={^)-@)ojus6$Qq_>}P(Mfz4*WEp!{S3uM@^0H2e! zP-S>$SWy#BtNs1lL88hbzgGEU26bn4l$uY1bGN*F(B$b?=7om7XKpvk{nJly5gB*F zDpv4aK>M>0wbC!+NSNOY##@ztfKa|E({St3ke6q$KUcY=JQ1HlP$ttvUER1Tpf)=| zzkG9bb^NQuTi~{uf3(SKujC2GHI9k_h0FAl7cxLn4J2t7(cc`cSx7R=kutj?_SO)q zblZH`{mPHmLO%6A7+#Ds8;=WlkYFxq^(a?P6lNL$bI8;>{nN|$;liit&bRDLUJs|c z4G2kD<0@=fSOBM`R)~>zFjZCx7=>p(h(pOd&Mh?>UuKa`ql!#Y1+=aI;2r_darXvqw%Bfbh)!EA(~gd0v2btgxehB_AG}*{Dsc>b~EE!?1~XrFYQ%Hu*M?^k^?5Pm&5(K75#^C^LXIz>{k8 z`VZG@i2Kc{TiY__OG;71$72=FR~~V~{;6hI0WzzPItONFzt-yy&`1s@u@K(N>}JCP z=u(DJFVAQ6DM-8(G+eGggZnw;n$^h|Xrqf-TNtdL$L>Sh3Usr;yM1zf9iMv|(rDITagSw8eA2TlF;QOW=MU`Vq*};tlC< zl}J1^G_zA#J-iHdYywzuj%p)Sy+^m&F{ho(E0V*)q_AT{G=8xLw}E_zW!$jm!-;~# z4-g*-hofrjPnEn*Zv8!LrTQQ+;&#FHXB^uPkG&Zt*hppBw9SxF09vPbPTI`pJkMKa z#r`-PbGFe2J!&E`yG%h3Hze)n-b8jx(%(YYYmLHk!{67~MZA<2-;#ojX1Vtb5X67K ziRliak<=a;EsV;$O_z}3h{MlBL^V&QkeCrfCvhKxhx8dRHthbD*HIX;!wPNK<(_vB zK_oMtHc1tabwH_PEgL&T1d$8pHSogMq z9&m0e1YEpl3I|QBPx`XLfY4|%shU(YAHWm|e)3I;{v|uRn6qHkw{eB=6zA8eiYF=J ztln{b+o6lZ^sO1mxuZ3`o;MYW4&y+Vetp5`Et4_cB-d|~mm9z<|? zO6LHLJ+&{&M+ft)VUD&`I}z7Xovv!HoH+f=&h0<4Cb!8XCgwtaN2iV`ItWkmFuokKQY+Eys-4DiiM#@$j?~m+E;J zXORay5X1TgybWXr5iqY=%~ry2qvd8$N?77=MvMUW?V|u7Cyn$Na5=c>J*^Nc=phlz zV%Sx6K5t14|9x4ZW`b(2#+$Wvy>zsqy2@es_A0J8+oZ&1)ul+Ax5R8ROD$ieV6M!> zKP1nphUUZm+z>c;M!&|MLjVn9;c@93`|&6kyF08sk=KOQ>Lpe3mv3ZPM*Y|)M{6aZ zS^_Ax36V|Gy1M=oK_JE*DNWTd3st9Bw1$6elUMUAVI5I*!I(wFk+<6xj&xs+-_@SGE3%$ zPPs=nSyB`~2**~UqS4c{aUyqbwoyp0Cx)>BobzcGY0lxE&z49w3E#rPHsaWmk6Bj7 zfa;NOE?&-6UWLeULdfv@YNH|!H<-|=Ngks=)r;*f=A0I?iUfcx)DWD6npa(JV z{t)FT2fkP|HgJ-lLKJLFm~toAdrM8CeC%j%UtZDg{O5=x=4gBT5QC!T z5i8)7Z7b)CosS{x^lC!|PKOWoX4tF2?A3eUNCBK^@sa*d?67o-f?W=1GILSn{(hmJ z9_+^ttAO6w*)rH864CdK0`fnPo)n8teFU`OGtCr^C)Pnn2>&Y!z$jc!>l$xe1U?;Q zSAT&g@i`wmii?UO;a;75?8hBOAJ12RK*(XPwia)ta`cQmi}XFBb~ZZE98`kOW~G0< z%owFvAAsTBQkhdnH9*V{W;ypdz@Z51;cz_*Z}#TZKRny3Ck0sc9qx>^sP!Se(r=+( zL#gf8@1GqsL^j<;ig5Y_`6GoB6B2V{hFBu;cFI3|~6(5fJB`SM$Sw6?O#V zHt7EO&sPC4D+T0z+>gOjImTVXWn;>X?l z+wH;F-vbl1_W?+KfEYfy*vO#?A#Wky%mKqlfVH>LK`t1is}!?45HJ~z-r?91JorF1 z-`!1I=fls-v))Yp_PbG@Hm`;M=dWmj3|3ZF{#>uV6ksY20Sn}G=SufAUmXeVFqABb zw~A4NOf>xX-eIxVDyZJ26%|7fAtLHJLKrFTPWId{W|&p-^k@HM=y*8$<>spD=BPK8 zA@u6DlVo;S1&w3@C0I%-_;VI~9cyZDmh)G9mxD+SVLl-burgk`?oPeetdg}FPE!HY z$&Maj_kF!arKR>kdYDEr&meG7=zdMQ1Zw2{UDYM*@RpUA(zuwS^XR-4lSS_UBNxxm zY2*MW43`bJ^OJ^80Q?ImQ_V+t4bx)SQ5X>@&1 zyMdxHl)aMnKzaT2Oh4A~Ajw;Q?1!S1gDSvlI+f-O`G|*&cMJXrnQt=H``-z+zxL3z z>IW2`=#?$#@+ynyQ)N-0QU(VK2gA`DZX9rM8!uKA+ge*jh~K#b09d)PF&I|NZ*X67 z%+bEUgK+H~ZEcNRt*xC%e1F>S(6@QLZM?gESt^r!`xKXuvoOnE%SLOGR;#gr*JOYw zUm46>51h9r3e0UPf83&)9W3Mw4Hd%I!|2A)?QnM&b3AXkzjGETnwmmA^YG`=;TqGq)F+DHH{Om>GS-fUb#XSH|HjNJf4e{Z6V-O9CEfG7f8%R)5L3n^2P7T?K zKGZ12GA$Z*tzQYC?uaudHXf&MY@pE%z67@6mz?cqF*<+kp)UdEa>Z1Hl=m&F#sv24 z>gv_&i$Aw{$~uhrs20cvT_b%@+|Gf=r(OqvY>S^bJMCdGu>KO^ops zs~W(16PreEZhZLmJKKIjbt_bf_9Bk^lrFBWl@_xpszouo9~)EtnB^OEmR|)gWN6ox zd(MRt1tN|jKB?NCEJEQ4^}OQ`+79U||41UOdT1FyBLB~3++0NAcaQs0CImr!_H5)- zSH%}hBjQS@klv!dW|p`p2aVcS6imnu4nz68QfJd4u+>z;etSQMyetaS4c5{b*dAm# z&ZYrf`jG!I;w3ODZ;l6Fs9iNb5d(f=OFk|m!BZca_jL#?`I>l49fSp!9VQY}U_cG! zg!{nf=Cw1YpAQyKc#}v5!!N2ynWorB0JAj-=ED2sc2KWnE^MI=R>*feuhT#oluTnO#}*yk?X(q0{4osr?4n*i=H54$tJ!5jUEp9s<+W(t5bIaY()r;X2cic72H z_Tt%oEHt>c0`Peh%RR=NMyeogl=`>2k{iX8*8lS+wDXQmdu(i7oy5@!-6<`z)DAI7 z7&jF$%-UlcuPpmr57vbBOvir1N6}=3o^utu&v-N>g;j@zQCE|brZBBAF`>c{4WCPlI93EE>#!^WMpTq7 zTcjX0)$XoEY&U>iYBT3AG&bHXx1KB-jq3oPP+SZ}S~9@!pr@lf(|uU^u81`|E9?4n zQr7$Zel))?z^qjmGS8C4z~ZQv|2LCuEVnDy#hzI!cD>&Wm1rtS=4~exoHZme5*PTO zp!F`c+B9C8TE-9u(*zg}gRcQtvRpag@s=&r{5vs4!KYY)!XL6}GhBR$3v^ zW)C?#(2*-ni3eg)z0A+>w4`)^V|s}Rie1W<5_dBhC%=|1)NBYdcg=cgIEl@`_I*K( zW)mOydik0hCkz3G-KD7}K*Fm>*(W}+r^_&>8YO%N;s3We2_N?|mPiqT4GNV}#pMx@ki0e>lPD=! zzVvL8pu4CB4*p;orsWKq{mCZr$i0^C?xCtxlnca!pb={^$Z~(8tT7!gtvMp$Djovn z&2q~hpwkQA&+nXUsl^D`=l6Z~hE}t(divc^kCDhA7(dErc6iXD9qA||Q(5Xu zQOT|5JX45YGR* z{MO(o>;IxuK;jXw{QYv?(QKy5DnQcI^i8rr0f-_Xa709SPiZI-T3NlLL`1y2&yr;P z<24EO#@Nd}Ar=&9qeZHKMCTfI3j_6>00+7NnP#O^Osur5w||E))M=?n_&V*)lARnq zuwPpoZyWClEQix#QoOc-q(Xf$)85~Y&w0}q%NZ7CEy{q)xE-nyM5h;X6~Fi2rq5S4 zd8IC=7Y**36rYxb-Rw@jPnY>46VG7;S6Lkts1zimIES}fw*yKH&?M=n%_04Z1~;@7 z&Xo%+GOnWc?_Xlza~5)P_~<;N#sO-1iOBQLy)TAw^xyO{gJg~AY6q=Myho0l@fMW^ z{sryz?bSDHxN~g8z`!9WQRc1Owtl7A;#?9JJ=1+0tO^wh3Ngxs*AIeME#ZXf^B?|O zvITDHjg;FANT;{ACJNA;)3OO_AI;)ffr?#Hbh={L1hDbX5x3``wYPIFa)5cyJ)Dy` zOwE>k@tyt~qptn!^{Y8p4AM#a)?9VT#>F9y+s1VW*OJ^o0ymY+c9{NWf6a>A-dqU~ z`*`{^g2p0*;5{+1Z~rIHvU(O-7<};CgUpU`8veu98^^HN8#hTx>4+NIfZezZ^81SO z{X1Mdbhp7$n@a~cIgCV#xOcx>kAUyXMhMRoE=Ck zOA-`(NCxRsuXSkmNBc@Dgpv0sh4hL4`Jck)5HgciKV`KyN@K5lZD2#dL;~roXQ7bF zSYA#g^m+{Hg`eNyTG}tv_5X2c$V#o30|1FHZBx$cz=9Y>u)KkVG;DWVXXFU#qj$GO zriL!mp0i)EUm1}{uxOiRrR0~H;RMAIoCe>cKftgYt&~1|P=y^Q7`6rM>*Acf`jza7 zy57GN6&Kg?Wc)LHih>jZ{YU1eq&Ry8IXA)mvX%~7EPOqY`CXzOpM4h;d^_|ClW^T; ze@f7DgeW5Qpa=eaSkXL@Pi+;*>6JLl??1C+ zbv$K+5Kt)*>^_!>pDw};70HY2Rn;iLgsxI<~AY%Ka|y4ZFbvT0RSCbN|&E~lp5 z?>Vs8-Wl^an14ai%>qLAiGW0?=wyK=H&?ZCqlZNkBhU^f%dM26Su$5@H9?B#f@%Nk zs-a|fl7LKy`s2rAzMXx<^hVUPZ@H7D7m2!sxh@4!wrNf#dn0ZB>qQ*o`qq}!&o5(8J6 z__Np2+^!DpATG5~{|E?v&`+DLiKj(rNK^I~>g5?iO*JwkJ=|w#F}vXz9?%}GT5*3v zQ1vJ&t-B-T&1XSM;}K%{>U}cef!wd9-vT_(PoVJqSlbM4$cxdf4Bk65c6MAcKpha{ zY}>!U)8{gzPS5rTCvufB_AAtTQU+{ba-c=QNWs)C?B7m9!_E%5!BDtoQg}^5cEf2A zn{R|Dx@>o+KEaNOq~5^b#woG@mT&6UI9T-1BDoVCz`i#r0e_(f?&h8X!8iB!^$NiqBD<-1ib3f=+F;R)sx>kU#V8HtGD~mRwqFnqG zM*XV9zCbPAl{)lojcj^L;=pyn8HVxppOeOFEa#zPZ^^X+wj`3itT2y-jm zXPe87`t2shjcSw%RBx2APb`A{Gg#L>HR1pR z9mPC4kz0Zzd)JOd7>QmVN+@L@WRMRfg0Jm=&~gmGsW4UeJyj zJ%{+Zvu8`s6*FeX)GM>$M&v~I4;J~w$Fph?bq&9MCnv`WTOBI)k9;dDdxpo<)=LF) z6su(wuRvq9n5$m+*E4Ek|5Cl%g8$Sf6Qh5JxECjnjw^VX0}(P>Y;dl3eNyM)4wpwK z2hK91W~CEQ*?=m&T8j8zp0~M2TNzVcZej}{KeI_$t!nGXSF2%BI4ob_=G1@QNyV}3 zEtPZfu%QG?VsHF(vCporp9jI6rAk;};PKw>r^WhsB>#@->D#s5@+!023j^NkRFM5O z!YX+JLWqX~we}##F|Agn!+=SPmB|D(Rm1yr{Ofoyc7!HJPus@t%Pf0#ip8vP`A0=h zU=#2#U{x555O0ijzE_X?0y({34y5z*mQX zTt0!Cwgn9nzZ zD*y+H?5E{T3>7%OlT$Rn_{-)aTK(YcPn1405nkgSEX#?f%)SC{)& zll}NF0S6p%`evrb(e;p)6)9n zF=~>-8w|*$J9hRPvMZpQhjjgTl^A+-EjTu&2Kb;7VV82nr@t-@q9QtW&FV7_MGo>y z5~=H|!cS=_fBdWUTIqI~e_fus7rtj-AI+nTj02m7P76loKF~s1JHUg_pNRS&W%HAA z!Cz-5<~7K;-QKz`f{e_mlp&8T6~{{>d=`-HHT+dg8Of7L2QrU-GxihRn_G4{Kja(l zAB3_X==V}nAQm^hNWh2E>#Cl-gUHb39Wv1cZLv}%;LRk0w)OkuW9f8oM70A|u z_GVq>kq_Haf#HaXeA2(YO8oyC^a>7|Jnr0)NuEfvfT;feWn)30E`+z5ARt{H1*kJO z_p}HIyMnl4$pwk0d8dHY+*B)P0;iQ)JjcgmX4i{XPxFGXyLIxv^D33+xL%#1SS`4H zJ^ph{0crxs=>Gtt%*!{}yCdykdp;Hrw#fbPkD#Hjo2h{GL%Q7-IoOQ1mpgyp#(q-T z?cq4p^n7(XfQc?e3XAwV3?HdPzr4aN!9d-guMMQ(sEq_{21s(?Y^TxN0;l?HeCvzT z$b=Bg7y47XwlX;L`)9bEty~CD+hn)f<2Fil3YjCu{O*5_o?%d5%TW&h`GI(3qPUx<|NECG3bgg!(A`pAr-AIej<~SaF2E3y5k8ZJrkkCAUxtEjH>QI)JW+Uvj zWC`F(pn1B0;#nHY4KZr_szMD(B`8LtLyG$&2J3|z;|>Y9YpmWrjFjgGqaxq^s0g=E zd&%w2gX>rZ?DA@g#+$R3L9g#aAE`*MA?#(n z@&g#Lq^3w?Mm06HA&_z}Q3gNI{`BagVP$LtYH%`v53h3O8+=g? zi@2E-uR##8+!*WxTYR-)v7sI>0t5twbgixPmRoC|#ud>nSw0Mt$C@fj0f>JIwbUy; z0S>Ndbn^29GWWr)>9d2bE=;5hoQ@?2gbW)w5I02V193y^)ncsaRv55?K&rK1<;c7C z(^_e}fV@`;eT>uHt%nMA^LCxjH8(M8BFJ`1eZ4f;05R4AjZF7*qT2rC#g}AcnAuwC zet1`r#8Rpdj!-!E?FCtcBu}O#l)RSe2e=lHi?S8^%(TXVlI<@3@v*w)LSiHTHlnJZpMxjlMkE+S;1S@4Eh(yxf7;!|hd_19%axVDKT;T*D3`lboCI zeuB4i#BAhl#8vuRMzx3-Ev(Ld$3mlGbI}X;649 zB))78&?7T3t8^2~e!Dkes_`Hl1twvU7lbw|m_hV`d3hAlKJ3c*YS0MZXA*2?Dqmkd z^b3S%^gM=+@ME*E`qBkocH+r!*!xY`NM-az_Rw~*ITXB9j*ZWS8UPTdU=gk`bjtmD z7Y$pqm`m|E%8+@FjFx2bb#$xzp|U#_>4x?kW3d?~zIcE8|H>WR8%~`dfH9W?crOT?ZaByOJXg0_5R#!Vt9UL9o+woa4ST!yN1rHu2 zAS_seohbVX(UTXf?pV6Nr?oY`EDQt_zrRXlE+nkX)mikP$DX4FtbsvrB9!11BX~7e zzpB*#YZ3v|so)*aui%_aP%oIZ0`L^knNzDHe>ZDiQ>><(-_N{lWhRqY5UJ=`>=TaX zT1f8V>p((=)E~rWDw^n8ePGw+d%?p7Z(dEwu`bY@l$Md{&Ohe)2Spk;9#WR|D$oQ# zjLa~LjACerGlH4$Lm`){G0Pxw(;OHvkH%{2i+m|LcV{GOc(sjje{VoI@8QwY+1Eiv zBBGCkkGMmeDE9ke)i^m#xB?i^WwhH&Sw(+j7Pju4fX#+8yuPLpnNi0SPBm-@f4tnpBw0ikI zQV?lP{7yq@JN@bOI*C^-h3obf=kfVdABs%UkIgA5DaO(K(1)CWe2kSC7>L6V ztWFfGPXi974k5Fmd=1iXVD~QpFdSB)i-Ye9D9q;c!2K4sdAaPr9!&}GhUesK4vqvG zNjl{^jBop0bs%?pwl%8Jb@_u;2xjg-1jYjOJP?bB%AwiIZW2$%a~7tgidGDd(WckL z(&=TCzt%@2oO?>Gdv#WRz_0@+vRtQ2CG|4bVZT2Wavzknva4c$TCw!{A>rDuu~}-N zYuSEA@BIVDBoJ|1DdG9xAaIdFQzz8c8%68!dr7|`*YmFOx14&NE{xO3f2HMfB621v z5kI&qbU3`aN+D!46_{UC2ZCF-anrz8MsNw#R#S(Lj5bGyKGTomu}^9Aw7``ZeS9WD zObd~E=7%)1caQ&x=WGsFBC^V5_>&>?0SVNZq(aR5UH7QQ7)=!$uRy zMR*im&69lw_tB<>TJw{5L}K7_Hp2-e#=aqe$WNdC@-$D^CVWNO|1eVot(^kZhT%I5 zq}N(n;9XecPjjr9#aULE?X7zvLMAPOUU73a`r+h9LFC=*9vR%6nftG0Qw zH6k{}OVKr5mM!S!c*fue#vWjTz$rjgcrsb=%>TzH{pm6jRINS~LMjk1oG%6H^LW~% zK8jYd*8+#lYO%Rvx|bHDNrj%ZQsIQ3Mtfe;q6%F8IkpKv!yn=1t4qefrrcTDP9xoY zz(_ok3BU`8JASS|qq;v8kNazzb;ZiN@3GdK*}kr7();*cby+oSlZH4f2e}$gvYFBE z+r_4QWXc0UE&K$5BO=~Ptsy=MaEqhb1sFGm*pCT*s1y)g;>j_&a1!l+_`DG`lAq|; z{(jHK$dY}Ee!IfYPtz@{VqViacW~ntG|IG^Z7CSs?`DcNy(*3QnUoPSBxHt{`TSSR zLa;Xm2IcCLsO+xr7qmHikI_GUD%}`#F16c6R)@j~I14)nWH_iWD{?{WGgsaikcnX? zq31mb$3vj`&YXPwO=rNT3=&WFE`Ty83?8+oF&%fIVHxPSzdO49Ww{+DlH`Y2;F>EWTf=4#LFRSXW(@d=IA^c<%E|DRa4>-QM)=rhE|+MqCfL zT91z|);hT|w`qniTXPPViGZtwWP*#-b2`sGK=yWX1&@h{OyCk+NCkrDH@Jz_?A$by zDJ|ix2Hw?nrLaNA$z!?{_9kYopqqZ?9eN6W5T)yRGy}VG0B{hzr7rI38X6?HsKT>=E@ZOrmVY?A^zq^cncG@Kxg3BWlX>OA3Wr7x18h}EsLQ9ln(8I5A|bCVJ*M> zcpecv$m!hJ|3e08P z3U&!?-o7f-`f0xWzR6=bxY+STyH`-CEtNhazvT2WDv<&!8V=0x%&cbXc-N&Ghz3xvNE zSRoN|c@MgO8#gyuTRQH=S)7soknxk3n^Lu{X+xR)WIC-WW`Gg6!q=l{*cFPcMEnpb z*>8~g`?#XXW*VtzN5~GNK)=HtL0h}bbfty3kkHIx$KRg|7y_ZKCCcCok=}2dK=?_C znUUFuKU}xqKH|a_-rAAN!p&8OhJ_pd(;QJ9OOg19o|i1FFwi}Nx?Qxg2=N|rhUb|w z#db7{nQ=H-OOzX%n|}NOCd)!X&ND!rY?fOvb`Jz2W02EHXQgfxBeUM}a_xnn%CwSW zwmV1Z1V@4Gem!*##I~FG-F=b5gV^s(m`|6MtgPxEGihJfI9dVMqB_N3lZ?3u4r%>U z0s!vYBy=e`EXbC@WY0tf8U*F}5ED&KY>HO`Fz`#;7CoeeDuIpA_U~`v>wcSn2KIkE zv|*dF7`2&5bJ|18*lzPta6dBYwDffVJZNx#gwsQ9Ds-42q=gwbf@UE7Ev%FRGV1<^ z-ReE{87XgG_P~%BOt-+QL5gs?qf=DC!!757{5k0BWe*(wE-Sd!3k6m~gPk%UcIS0O46(|s-~cMMW6y*|9%`g8-qKbJO1RmcAced3G5Zm`JR(p@E}WJAaLF5&;;C^U-UyOPazv|W=`bR;~n zK;v{N$eEf30SQVNK}C#(k3Duj`dqi&m;A!z7GDbBvHBg4RhTIr`AEX!Xe3E$Cz9dc zUlQ|HCfvCru@7qwu)z=4no$Tw6YTTe7

$0ITcj(KRIbdLxc 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 * *

- * 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 + * There are 2 ways to launch the application. + * - to use "in Memory" database. + * - to use the MongoDb as a database + * + * To run the application with "in Memory" database, just launch it without parameters + * Example: 'java -jar app.jar' + * + * To run the application with MongoDb you need to be installed the MongoDb + * in your system, or to launch it in the docker container. + * You may launch docker container from the root of current module with command: + * 'docker-compose up' + * Then you can start the application with parameter --mongo + * Example: 'java -jar app.jar --mongo' *

* * @see CacheStore @@ -86,7 +95,13 @@ public class App { // 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)); + 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); diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java index 589e9eca0..3d59d73de 100644 --- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java +++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java @@ -36,7 +36,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class CacheStore { /** - * Cach capacity. + * Cache capacity. */ private static final int CAPACITY = 3; diff --git a/caching/src/main/java/com/iluwatar/caching/LruCache.java b/caching/src/main/java/com/iluwatar/caching/LruCache.java index a9a93a4f6..70450d929 100644 --- a/caching/src/main/java/com/iluwatar/caching/LruCache.java +++ b/caching/src/main/java/com/iluwatar/caching/LruCache.java @@ -177,7 +177,7 @@ public class LruCache { } /** - * Che if Cache cintains the userId. + * Check if Cache contains the userId. * * @param userId {@link String} * @return boolean From 119abf3ee4552828ad32be9b8c34479d95e95dfd Mon Sep 17 00:00:00 2001 From: Conny Hansson <71334757+Conhan93@users.noreply.github.com> Date: Fri, 8 Oct 2021 19:34:47 +0200 Subject: [PATCH 20/25] Doc: Corrected a few spelling mistakes (#1840) --- active-object/README.md | 8 ++++---- .../java/com/iluwatar/activeobject/ActiveCreature.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/active-object/README.md b/active-object/README.md index 6e974034a..395c7f59f 100644 --- a/active-object/README.md +++ b/active-object/README.md @@ -11,7 +11,7 @@ tags: ## Intent -The active object design pattern decouples method execution from method invocation for objects that each reside in their thread of control. The goal is to introduce concurrency, by using asynchronous method invocation and a scheduler for handling requests. +The active object design pattern decouples method execution from method invocation for objects that each reside in their thread of control. The goal is to introduce concurrency, by using asynchronous method invocation, and a scheduler for handling requests. ## Explanation @@ -70,7 +70,7 @@ public abstract class ActiveCreature{ requests.put(new Runnable() { @Override public void run() { - logger.info("{} has started to roam and the wastelands.",name()); + logger.info("{} has started to roam the wastelands.",name()); } } ); @@ -82,7 +82,7 @@ public abstract class ActiveCreature{ } ``` -We can see that any class that will extend the ActiveCreature class will have its own thread of control to execute and invocate methods. +We can see that any class that will extend the ActiveCreature class will have its own thread of control to invoke and execute methods. For example, the Orc class: @@ -96,7 +96,7 @@ public class Orc extends ActiveCreature { } ``` -Now, we can create multiple creatures such as Orcs, tell them to eat and roam and they will execute it on their own thread of control: +Now, we can create multiple creatures such as Orcs, tell them to eat and roam, and they will execute it on their own thread of control: ```java public static void main(String[] args) { diff --git a/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java b/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java index 479dc0643..9be60505d 100644 --- a/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java +++ b/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java @@ -82,7 +82,7 @@ public abstract class ActiveCreature { } /** - * Roam in the wastelands. + * Roam the wastelands. * @throws InterruptedException due to firing a new Runnable. */ public void roam() throws InterruptedException { From 9513d2be583135141589bc6bca20534b9a211d9d Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 8 Oct 2021 20:36:02 +0300 Subject: [PATCH 21/25] docs: add Conhan93 as a contributor for doc (#1844) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 453fdfe3b..c54a315f7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1604,6 +1604,15 @@ "contributions": [ "code" ] + }, + { + "login": "Conhan93", + "name": "Conny Hansson", + "avatar_url": "https://avatars.githubusercontent.com/u/71334757?v=4", + "profile": "https://github.com/Conhan93", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 4, diff --git a/README.md b/README.md index ae74f167b..48121c041 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![All Contributors](https://img.shields.io/badge/all_contributors-176-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-177-orange.svg?style=flat-square)](#contributors-)
@@ -339,6 +339,9 @@ This project is licensed under the terms of the MIT license.
Nagaraj Tantri

💻
Francesco Scuccimarri

💻 + +
Conny Hansson

📖 + From 0255111b4e366ec9b9510369c35f85608ad2c1a5 Mon Sep 17 00:00:00 2001 From: Muklas Rahmanto Date: Sat, 9 Oct 2021 00:45:59 +0700 Subject: [PATCH 22/25] translation: Add Indonesian translation (#1841) * Create README.md in id * Add README.md in id url --- README.md | 2 +- localization/id/README.md | 333 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 localization/id/README.md diff --git a/README.md b/README.md index 48121c041..640baae3d 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), [**id**](localization/id/README.md)
diff --git a/localization/id/README.md b/localization/id/README.md new file mode 100644 index 000000000..15ccd7729 --- /dev/null +++ b/localization/id/README.md @@ -0,0 +1,333 @@ + + +# Implementasi design patterns pada Java + +![Java CI](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI/badge.svg) +[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) +[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) +[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +[![All Contributors](https://img.shields.io/badge/all_contributors-176-orange.svg?style=flat-square)](#contributors-) + + +
+ +Baca dalam bahasa lain : [**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), [**id**](localization/id/README.md) + +
+ +# Pengenalan + +Design patterns adalah best practice yang dapat digunakan programmer untuk memecahkan masalah umum saat merancang aplikasi atau sistem. + +Design patterns dapat mempercepat proses pengembangan dengan menyediakan kode yang teruji, terbukti paradigma pengembangan. + +Menggunakan kembali design patterns membantu mencegah masalah-masalah kecil yang dapat menyebabkan masalah yang lebih besar, dan juga meningkatkan keterbacaan kode untuk programmer dan arsitek yang +familiar dengan pola. + + +# Mulai + +Situs ini menampilkan Design Patterns pada Java. Solusi-solusi yang terdapat pada situs ini telah dikembangkan oleh programmer dan arsitek yang berpengalaman dari komunitas open source. Pola-polanya dapat dilihat pada deskripsi atau dengan melihat source code mereka. Contoh-contoh source code memiliki komentar yang baik dan dapat dianggap sebagai tutorial pemrograman tentang cara menerapkan pola tertentu. Kami menggunakan teknologi Java open source yang populer dan telah terbukti. + +Sebelum anda masuk kedalam materinya, anda harus familiar dengan macam-macam [Software Design Principles](https://java-design-patterns.com/principles/). + +Semua desain seharusnya sesimpel mungkin. Anda harus mulai dengan KISS, YAGNI, dan prinsip Do The Simples Thing That Could Possibly Work. hanya boleh diperkenalkan ketika dibutuhkan untuk praktik +kemungkinan diperpanjang. + +Setelah Anda terbiasa dengan konsep-konsep ini, Anda dapat mulai belajar +[design patterns yang tersedia](https://java-design-patterns.com/patterns/) menggunakan cara-cara berikut + + - Cari spesifik pattern berdasarkan namanya. Apabila tidak menemukannya tolong lapor pattern baru [disini](https://github.com/iluwatar/java-design-patterns/issues). + - Gunakan tag-tag seperti `Performance`, `Gang of Four` atau `Data access`. + - Gunakan kategori dari pattern, `Creational`, `Behavioral`, dan sebagainya. + +Semoga Anda menemukan solusi Object-Oriented yang bermanfaat untuk arsitektur Anda dari yang disajikan di situs ini dan mempelajarinya dengan senang seperti kami mengembangkannya. + +# Cara berkontribusi + +Jika anda memiliki keinginan untuk berkontribusi pada proyek ini anda akan menemukan informasi yang revelan pada halaman [developer wiki](https://github.com/iluwatar/java-design-patterns/wiki). Kami akan membantu anda dan menjawab pertanyaan anda pada [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns). + +# License + +Proyek ini dilisensikan di bawah ketentuan lisensi MIT. + +# Contributors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Ilkka Seppälä

📆 🚧 🖋

amit1307

💻

Narendra Pathai

💻 🤔 👀

Jeroen Meulemeester

💻

Joseph McCarthy

💻

Thomas

💻

Anurag Agarwal

💻

Markus Moser

🎨 💻 🤔

Sabiq Ihab

💻

Amit Dixit

💻

Piyush Kailash Chaudhari

💻

joshzambales

💻

Kamil Pietruszka

💻

Zafar Khaydarov

💻 📖

Paul Campbell

💻

Argyro Sioziou

💻

TylerMcConville

💻

saksham93

💻

nikhilbarar

💻

Colin But

💻

Ruslan

💻

Juho Kang

💻

Dheeraj Mummareddy

💻

Bernardo Sulzbach

💻

Aleksandar Dudukovic

💻

Yusuf Aytaş

💻

Mihály Kuprivecz

💻

Stanislav Kapinus

💻

GVSharma

💻

Srđan Paunović

💻

Petros G. Sideris

💻

Pramod Gupta

👀

Amarnath Chandana

💻

Anurag870

💻 📖

Wes Gilleland

💻

Harshraj Thakor

💻

Martin Vandenbussche

💻

Alexandru Somai

💻

Artur Mogozov

💻

anthony

💻

Christian Cygnus

💻

Dima Gubin

💻

Joshua Jimenez

💻

Kai Winter

💻

lbroman

💻

Przemek

💻

Prafful Agarwal

🖋

Sanket Panhale

🖋

staillebois

💻

Krisztián Nagy

💻

Alexander Ivanov

💻

Yosfik Alqadri

💻

Agustí Becerra Milà

💻

Juan Manuel Suárez

💻

Luigi Cortese

💻

Katarzyna Rzepecka

💻

adamski.pro

💻

Shengli Bai

💻

Boris

💻

Dmitry Avershin

💻

靳阳

💻

hoangnam2261

💻

Arpit Jain

💻

Jón Ingi Sveinbjörnsson

💻

Kirill Vlasov

💻

Mitchell Irvin

💻

Ranjeet

💻

PhoenixYip

💻

M Saif Asif

💻

kanwarpreet25

💻

Leon Mak

💻

Per Wramdemark

💻

Evan Sia Wai Suan

💻

AnaghaSasikumar

💻

Christoffer Hamberg

💻

Dominik Gruntz

💻

Hannes

💻

Leo Gutiérrez Ramírez

💻

Zhang WH

💻

Christopher O'Connell

💻

George Mavroeidis

💻

Hemant Bothra

💻 🎨

Kevin Peters

💻

George Aristy

💻

Mahendran Mookkiah

💻

Azureyjt

💻

gans

💻

Matt

🖋

Gopinath Langote

💻

Hoswey

💻

Amit Pandey

💻

gwildor28

🖋

田浩

🖋

Stamatis Pitsios

💻

qza

💻

Rodolfo Forte

🖋

Ankur Kaushal

💻

Ovidijus Okinskas

💻

Robert Kasperczyk

💻

Tapio Rautonen

💻

Yuri Orlov

💻

Varun Upadhyay

💻

Aditya Pal

💻

grzesiekkedzior

💻 👀

Sivasubramani M

💻

Sami Airaksinen

💻

Janne Sinivirta

💻

Boris-Chengbiao Zhou

🖋

Jacob Hein

🖋

Richard Jones

🖋

Rachel M. Carmena

🖋

Zaerald Denze Lungos

🖋

Lars Kappert

🖋

Mike Liu

🌍

Matt Dolan

💻 👀

Manan

👀

Nishant Arora

💻

Peeyush

💻

Rakesh

💻 👀

Wei Seng

💻

Ashish Trivedi

💻

洪月阳

💻

xdvrx1

👀 🤔

Subhrodip Mohanta

💻 👀 🚧

Bethan Palmer

💻

Toxic Dreamz

💻

Edy Cu Tjong

📖

Michał Krzywański

💻

Stefan Birkner

💻

Fedor Skvorcov

💻

samilAyoub

💻

Vladislav Golubinov

💻

Swaraj

💻

Christoph Flick

📖

Ascênio

👀

Domenico Sibilio

📖

Akash Chandwani

👀

Pavlo Manannikov

💻

Eiman

💻

Rocky

📖

Ibrahim ali abdelghany

👀

Girish Kulkarni

📖

Omar Karazoun

💻

Jeff Evans

💻

Vivek Singh

💻

siavash

💻

ruchpeanuts

📖

warp125

🌍

KHADIR Tayeb

🌍

ignite1771

💻

Halil Demir

🌍

Rohit Singh

💻

byoungju94

💻

Moustafa Farhat

🌍

Martel Richard

💻

va1m

💻

Noam Greenshtain

💻

yonghong Xu

📖

jinishavora

👀 💻

Elvys Soares

💻

zWeBrain

💻

余林颖

🌍

Alain

🌍

VR

📖

JackieNim

💻

EdisonE3

💻

Tao

💻

Juan Manuel Abate

🌍

Xenilo137

💻

Samuel Souza

💻

Marlo Henrique

🌍

AndriyPyzh

💻

karthikbhat13

💻

Morteza Adigozalpour

💻

Nagaraj Tantri

💻

Francesco Scuccimarri

💻
+ + + + + From ddb9b14eedfe317e4c95571bc5197f2e5e1ed596 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 8 Oct 2021 20:47:25 +0300 Subject: [PATCH 23/25] docs: add muklasr as a contributor for translation (#1845) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] 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 c54a315f7..1a192ede7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1613,6 +1613,15 @@ "contributions": [ "doc" ] + }, + { + "login": "muklasr", + "name": "Muklas Rahmanto", + "avatar_url": "https://avatars.githubusercontent.com/u/43443753?v=4", + "profile": "http://muklasr.medium.com", + "contributions": [ + "translation" + ] } ], "contributorsPerLine": 4, diff --git a/README.md b/README.md index 640baae3d..cdcb49c4d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![All Contributors](https://img.shields.io/badge/all_contributors-177-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-178-orange.svg?style=flat-square)](#contributors-)
@@ -341,6 +341,7 @@ This project is licensed under the terms of the MIT license.
Conny Hansson

📖 +
Muklas Rahmanto

🌍 From 0a73ead12dd9302536a196a9d7a4d8c26104c33c Mon Sep 17 00:00:00 2001 From: Vadim <38704817+VxDxK@users.noreply.github.com> Date: Mon, 11 Oct 2021 21:43:58 +0300 Subject: [PATCH 24/25] translation: Add Russian translation (#1846) * Add ru url to README.md * Create russian README.md --- README.md | 2 +- localization/ru/README.md | 345 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 localization/ru/README.md diff --git a/README.md b/README.md index cdcb49c4d..9cc1e1bce 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), [**id**](localization/id/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), [**id**](localization/id/README.md), [**ru**](localization/ru/README.md)
diff --git a/localization/ru/README.md b/localization/ru/README.md new file mode 100644 index 000000000..599a778f4 --- /dev/null +++ b/localization/ru/README.md @@ -0,0 +1,345 @@ + + +# Шаблоны проектирования на Java + +![Java CI](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI/badge.svg) +[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) +[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) +[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +[![All Contributors](https://img.shields.io/badge/all_contributors-178-orange.svg?style=flat-square)](#contributors-) + + +# Введение +Шаблоны проектирования - лучший метод для решения проблем, возникающих +во время разработки приложения или системы. + +Шаблоны проектирования могут ускорить процесс разработку путем предоставления +проверенных моделей/парадигм. + +Использование шаблонов повторно поможет избежать частых проблем, из-за которых +происходят проблемы. А еще это повысит читаемость кода для программистов и +архитекторов, знакомых с шаблонами. + +# Начало работы + +В этом репозитории размещены шаблоны проектирования на Java. Они были разработаны +программистами из open source сообщества. Шаблон может быть выбран +из его описания или просмотром его исходного кода. Код хорошо задокументирован, +его можно рассматривать, как учебник по программированию о конкретном шаблоне. +Мы используем самые популярные (прошедшие огонь, воду и медные трубы) технологии, +основанные только на ПО с открытым исходным кодом. + +Преждем чем нырнуть в материал, тебе следует ознакомиться с различными +[принципами разработки ПО(англ)](https://java-design-patterns.com/principles/). + +Все конструкции должны быть максимально простыми. Не делай лишнюю функциональность, +которая скорей всего не пригодиться, а просто создай простую штуку, что сможет +работать. Усложнять и вводить паттерны необходимо, лишь когда масштабируемость +действительно нужна. + +Как только ознакомитесь с этими концепциями, приступайте к изучению +[доступных шаблонов проектирования(англ)](https://java-design-patterns.com/patterns/) любым +из следующих методов: + +- Ищите шаблон по имени. Не смогли найти такой? Сообщите об этом [здесь](https://github.com/iluwatar/java-design-patterns/issues). +- Используя тэги, например `Performance`, `Gang of Four` или `Data access`. +- Используя категории шаблонов `Creational`, `Behavioral` и другие. + +Надеемся объективно-ориентированные решения, представленные здесь будут вам +полезны и найдут место в ваших проектах, а также вы получите такое же удовольствие +от их изучения, какое получили мы во время их разработки. + +# Как принять участие в разработке + +Если вы захотите принять участие в жизни проекта, вся полезная информация находится на +нашей [вики(англ)](https://github.com/iluwatar/java-design-patterns/wiki). Мы можем помочь +и ответить на твои вопросы в чате [Gitter](https://gitter.im/iluwatar/java-design-patterns). + +# Лицензия + +Проект основывается на тезисах лицензии MIT. + +# Разработчики + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Ilkka Seppälä

📆 🚧 🖋

amit1307

💻

Narendra Pathai

💻 🤔 👀

Jeroen Meulemeester

💻

Joseph McCarthy

💻

Thomas

💻

Anurag Agarwal

💻

Markus Moser

🎨 💻 🤔

Sabiq Ihab

💻

Amit Dixit

💻

Piyush Kailash Chaudhari

💻

joshzambales

💻

Kamil Pietruszka

💻

Zafar Khaydarov

💻 📖

Paul Campbell

💻

Argyro Sioziou

💻

TylerMcConville

💻

saksham93

💻

nikhilbarar

💻

Colin But

💻

Ruslan

💻

Juho Kang

💻

Dheeraj Mummareddy

💻

Bernardo Sulzbach

💻

Aleksandar Dudukovic

💻

Yusuf Aytaş

💻

Mihály Kuprivecz

💻

Stanislav Kapinus

💻

GVSharma

💻

Srđan Paunović

💻

Petros G. Sideris

💻

Pramod Gupta

👀

Amarnath Chandana

💻

Anurag870

💻 📖

Wes Gilleland

💻

Harshraj Thakor

💻

Martin Vandenbussche

💻

Alexandru Somai

💻

Artur Mogozov

💻

anthony

💻

Christian Cygnus

💻

Dima Gubin

💻

Joshua Jimenez

💻

Kai Winter

💻

lbroman

💻

Przemek

💻

Prafful Agarwal

🖋

Sanket Panhale

🖋

staillebois

💻

Krisztián Nagy

💻

Alexander Ivanov

💻

Yosfik Alqadri

💻

Agustí Becerra Milà

💻

Juan Manuel Suárez

💻

Luigi Cortese

💻

Katarzyna Rzepecka

💻

adamski.pro

💻

Shengli Bai

💻

Boris

💻

Dmitry Avershin

💻

靳阳

💻

hoangnam2261

💻

Arpit Jain

💻

Jón Ingi Sveinbjörnsson

💻

Kirill Vlasov

💻

Mitchell Irvin

💻

Ranjeet

💻

PhoenixYip

💻

M Saif Asif

💻

kanwarpreet25

💻

Leon Mak

💻

Per Wramdemark

💻

Evan Sia Wai Suan

💻

AnaghaSasikumar

💻

Christoffer Hamberg

💻

Dominik Gruntz

💻

Hannes

💻

Leo Gutiérrez Ramírez

💻

Zhang WH

💻

Christopher O'Connell

💻

George Mavroeidis

💻

Hemant Bothra

💻 🎨

Kevin Peters

💻

George Aristy

💻

Mahendran Mookkiah

💻

Azureyjt

💻

gans

💻

Matt

🖋

Gopinath Langote

💻

Hoswey

💻

Amit Pandey

💻

gwildor28

🖋

田浩

🖋

Stamatis Pitsios

💻

qza

💻

Rodolfo Forte

🖋

Ankur Kaushal

💻

Ovidijus Okinskas

💻

Robert Kasperczyk

💻

Tapio Rautonen

💻

Yuri Orlov

💻

Varun Upadhyay

💻

Aditya Pal

💻

grzesiekkedzior

💻 👀

Sivasubramani M

💻

Sami Airaksinen

💻

Janne Sinivirta

💻

Boris-Chengbiao Zhou

🖋

Jacob Hein

🖋

Richard Jones

🖋

Rachel M. Carmena

🖋

Zaerald Denze Lungos

🖋

Lars Kappert

🖋

Mike Liu

🌍

Matt Dolan

💻 👀

Manan

👀

Nishant Arora

💻

Peeyush

💻

Rakesh

💻 👀

Wei Seng

💻

Ashish Trivedi

💻

洪月阳

💻

xdvrx1

👀 🤔

Subhrodip Mohanta

💻 👀 🚧

Bethan Palmer

💻

Toxic Dreamz

💻

Edy Cu Tjong

📖

Michał Krzywański

💻

Stefan Birkner

💻

Fedor Skvorcov

💻

samilAyoub

💻

Vladislav Golubinov

💻

Swaraj

💻

Christoph Flick

📖

Ascênio

👀

Domenico Sibilio

📖

Akash Chandwani

👀

Pavlo Manannikov

💻

Eiman

💻

Rocky

📖

Ibrahim ali abdelghany

👀

Girish Kulkarni

📖

Omar Karazoun

💻

Jeff Evans

💻

Vivek Singh

💻

siavash

💻

ruchpeanuts

📖

warp125

🌍

KHADIR Tayeb

🌍

ignite1771

💻

Halil Demir

🌍

Rohit Singh

💻

byoungju94

💻

Moustafa Farhat

🌍

Martel Richard

💻

va1m

💻

Noam Greenshtain

💻

yonghong Xu

📖

jinishavora

👀 💻

Elvys Soares

💻

zWeBrain

💻

余林颖

🌍

Alain

🌍

VR

📖

JackieNim

💻

EdisonE3

💻

Tao

💻

Juan Manuel Abate

🌍

Xenilo137

💻

Samuel Souza

💻

Marlo Henrique

🌍

AndriyPyzh

💻

karthikbhat13

💻

Morteza Adigozalpour

💻

Nagaraj Tantri

💻

Francesco Scuccimarri

💻

Conny Hansson

📖

Muklas Rahmanto

🌍
+ + + + + From d247b6ed696f31f176fe4d83585bd13516b26f63 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 21:45:21 +0300 Subject: [PATCH 25/25] docs: add VxDxK as a contributor for translation (#1848) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] 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 1a192ede7..5a91ce748 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1622,6 +1622,15 @@ "contributions": [ "translation" ] + }, + { + "login": "VxDxK", + "name": "Vadim", + "avatar_url": "https://avatars.githubusercontent.com/u/38704817?v=4", + "profile": "https://github.com/VxDxK", + "contributions": [ + "translation" + ] } ], "contributorsPerLine": 4, diff --git a/README.md b/README.md index 9cc1e1bce..a87c3d87c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![All Contributors](https://img.shields.io/badge/all_contributors-178-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-179-orange.svg?style=flat-square)](#contributors-)
@@ -342,6 +342,7 @@ This project is licensed under the terms of the MIT license.
Conny Hansson

📖
Muklas Rahmanto

🌍 +
Vadim

🌍