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/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/etc/caching.png b/caching/etc/caching.png deleted file mode 100644 index b6ed703ab..000000000 Binary files a/caching/etc/caching.png and /dev/null differ diff --git a/caching/pom.xml b/caching/pom.xml index eb610dbc9..d1e75c155 100644 --- a/caching/pom.xml +++ b/caching/pom.xml @@ -38,19 +38,14 @@ test - org.mongodb - mongodb-driver - 3.12.1 + 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 AppManager --> CacheStore/LRUCache/CachingPolicy --> DBManager} + * {@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> + * DBManager} + *

+ * + *

+ * 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 @@ -61,23 +65,67 @@ 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; + + /** + * 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(String[] args) { - AppManager.initDb(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests + 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). - AppManager.initCacheCapacity(3); - var app = new App(); + 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); + } + + /** + * 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; } /** @@ -85,14 +133,14 @@ public class App { */ 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"); } /** @@ -100,21 +148,21 @@ public class App { */ public void useReadThroughAndWriteAroundStrategy() { LOGGER.info("# CachingPolicy.AROUND"); - AppManager.initCachingPolicy(CachingPolicy.AROUND); + appManager.initCachingPolicy(CachingPolicy.AROUND); var userAccount2 = new UserAccount("002", "Jane", "She is a girl."); - AppManager.save(userAccount2); - LOGGER.info(AppManager.printCacheContent()); - AppManager.find("002"); - LOGGER.info(AppManager.printCacheContent()); - userAccount2 = AppManager.find("002"); + 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(userAccount2); + LOGGER.info(appManager.printCacheContent()); + appManager.find("002"); + LOGGER.info(appManager.printCacheContent()); + appManager.find("002"); } /** @@ -122,23 +170,31 @@ public class App { */ public void useReadThroughAndWriteBehindStrategy() { LOGGER.info("# CachingPolicy.BEHIND"); - AppManager.initCachingPolicy(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); - 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()); + 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()); } /** @@ -146,20 +202,26 @@ public class App { */ public void useCacheAsideStategy() { LOGGER.info("# CachingPolicy.ASIDE"); - AppManager.initCachingPolicy(CachingPolicy.ASIDE); - LOGGER.info(AppManager.printCacheContent()); + 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); + 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 68c8a0d36..53489c83b 100644 --- a/caching/src/main/java/com/iluwatar/caching/AppManager.java +++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java @@ -23,65 +23,80 @@ package com.iluwatar.caching; -import java.text.ParseException; +import com.iluwatar.caching.database.DbManager; + import java.util.Optional; + 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 final class AppManager { +public class AppManager { + /** + * Caching Policy. + */ + private CachingPolicy cachingPolicy; + /** + * Database Manager. + */ + private final DbManager dbManager; + /** + * Cache Store. + */ + private final CacheStore cacheStore; - private static CachingPolicy cachingPolicy; - - private AppManager() { + /** + * 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 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. + * + * @param policy is a {@link CachingPolicy} */ - public static void initCachingPolicy(CachingPolicy policy) { + public void initCachingPolicy(final 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. + * + * @param userId String + * @return {@link UserAccount} */ - 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); } @@ -90,41 +105,55 @@ public final class AppManager { /** * Save user account. + * + * @param userAccount {@link UserAccount} */ - 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); } } - public static String printCacheContent() { - return CacheStore.print(); + /** + * Returns String. + * + * @return String + */ + public String printCacheContent() { + return cacheStore.print(); } /** * Cache-Aside save user account helper. + * + * @param userAccount {@link UserAccount} */ - private static void saveAside(UserAccount userAccount) { - DbManager.updateDb(userAccount); - CacheStore.invalidate(userAccount.getUserId()); + 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 static 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)); - return userAccount; - }) - .orElse(null); + 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); } } diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java index 156f024d4..3d59d73de 100644 --- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java +++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java @@ -23,9 +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 lombok.extern.slf4j.Slf4j; /** @@ -33,16 +35,34 @@ import lombok.extern.slf4j.Slf4j; */ @Slf4j public class CacheStore { + /** + * Cache capacity. + */ + private static final int CAPACITY = 3; - private static LruCache cache; + /** + * Lru cache see {@link LruCache}. + */ + private LruCache cache; + /** + * DbManager. + */ + private final DbManager dbManager; - private CacheStore() { + /** + * Cache Store. + * @param dataBaseManager {@link DbManager} + */ + public CacheStore(final DbManager dataBaseManager) { + this.dbManager = dataBaseManager; + initCapacity(CAPACITY); } /** * Init cache capacity. + * @param capacity int */ - public static void initCapacity(int capacity) { + public void initCapacity(final int capacity) { if (cache == null) { cache = new LruCache(capacity); } else { @@ -52,57 +72,64 @@ public class CacheStore { /** * Get user account using read-through cache. + * @param userId {@link String} + * @return {@link UserAccount} */ - 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; } /** * Get user account using write-through cache. + * @param userAccount {@link 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); } /** * Get user account using write-around cache. + * @param userAccount {@link UserAccount} */ - public static 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 + dbManager.updateDb(userAccount); + // Cache data has been updated -- remove older + cache.invalidate(userAccount.getUserId()); // version from cache. } else { - DbManager.writeToDb(userAccount); + dbManager.writeToDb(userAccount); } } /** * Get user account using read-through cache with write-back policy. + * @param userId {@link String} + * @return {@link UserAccount} */ - public static UserAccount readThroughWithWriteBackPolicy(String userId) { + public UserAccount readThroughWithWriteBackPolicy(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!"); + 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; @@ -110,12 +137,13 @@ public class CacheStore { /** * Set user account. + * @param userAccount {@link UserAccount} */ - public static 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(); - DbManager.upsertDb(toBeWrittenToDb); + dbManager.upsertDb(toBeWrittenToDb); } cache.set(userAccount.getUserId(), userAccount); } @@ -123,7 +151,7 @@ public class CacheStore { /** * Clears cache. */ - public static void clearCache() { + public void clearCache() { if (cache != null) { cache.clear(); } @@ -132,44 +160,51 @@ 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); + dbManager.disconnect(); } /** * Print user accounts. + * @return {@link String} */ - public static String print() { + public String print() { return Optional.ofNullable(cache) .map(LruCache::getCacheDataInListForm) .orElse(List.of()) .stream() .map(userAccount -> userAccount.toString() + "\n") - .collect(Collectors.joining("", "\n--CACHE CONTENT--\n", "----\n")); + .collect(Collectors.joining("", "\n--CACHE CONTENT--\n", "----")); } /** * Delegate to backing cache store. + * @param userId {@link String} + * @return {@link UserAccount} */ - public static 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 static 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 static void invalidate(String userId) { + public void invalidate(final String userId) { cache.invalidate(userId); } } 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/DbManager.java b/caching/src/main/java/com/iluwatar/caching/DbManager.java deleted file mode 100644 index 3284d3118..000000000 --- a/caching/src/main/java/com/iluwatar/caching/DbManager.java +++ /dev/null @@ -1,172 +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 static final String ERROR_MESSAGE_LOG = "Error connecting to MongoDB"; - - 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_MESSAGE_LOG, 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_MESSAGE_LOG, 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_MESSAGE_LOG, 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_MESSAGE_LOG, 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/LruCache.java b/caching/src/main/java/com/iluwatar/caching/LruCache.java index a320e35dc..70450d929 100644 --- a/caching/src/main/java/com/iluwatar/caching/LruCache.java +++ b/caching/src/main/java/com/iluwatar/caching/LruCache.java @@ -29,41 +29,83 @@ 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 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 +117,10 @@ 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 +135,10 @@ 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 +152,11 @@ 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 +176,43 @@ public class LruCache { } } - public boolean contains(String userId) { + /** + * Check if Cache contains 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} */ - public void invalidate(String userId) { + 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); } } + /** + * Check if the cache is full. + * @return boolean + */ public boolean isFull() { return cache.size() >= capacity; } + /** + * Get LRU data. + * + * @return {@link UserAccount} + */ public UserAccount getLruData() { return end.userAccount; } @@ -161,6 +228,8 @@ public class LruCache { /** * Returns cache data in list form. + * + * @return {@link List} */ public List getCacheDataInListForm() { var listOfCacheData = new ArrayList(); @@ -174,10 +243,14 @@ 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..1ec3af228 100644 --- a/caching/src/main/java/com/iluwatar/caching/UserAccount.java +++ b/caching/src/main/java/com/iluwatar/caching/UserAccount.java @@ -24,19 +24,28 @@ 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. + */ 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/DbManager.java b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java new file mode 100644 index 000000000..14d7247f8 --- /dev/null +++ b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java @@ -0,0 +1,52 @@ +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 { + /** + * Connect to DB. + */ + void connect(); + + /** + * Disconnect from DB. + */ + void disconnect(); + + /** + * 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 new file mode 100644 index 000000000..90ef432cc --- /dev/null +++ b/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java @@ -0,0 +1,25 @@ +package com.iluwatar.caching.database; + +/** + * 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 new file mode 100644 index 000000000..a9dd006f8 --- /dev/null +++ b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java @@ -0,0 +1,128 @@ +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; +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; + +/** + * Implementation of DatabaseManager. + * implements base methods to work with MongoDb. + */ +@Slf4j +public class MongoDb implements DbManager { + 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() { + 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(); + } + + /** + * Read data from DB. + * + * @param userId {@link String} + * @return {@link UserAccount} + */ + @Override + public UserAccount readFromDb(final String userId) { + 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; + } + } + + /** + * Write data to DB. + * + * @param userAccount {@link UserAccount} + * @return {@link UserAccount} + */ + @Override + public UserAccount writeToDb(final UserAccount userAccount) { + 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) { + 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) { + 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 new file mode 100644 index 000000000..0c9a14ec9 --- /dev/null +++ b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java @@ -0,0 +1,78 @@ +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 { + /** + * Virtual DataBase. + */ + private Map db; + + /** + * Creates new HashMap. + */ + @Override + public void connect() { + db = new HashMap<>(); + } + + @Override + public void disconnect() { + db = 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; + } + + /** + * 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); + } +} 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..56deee71d --- /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..00687084f --- /dev/null +++ b/caching/src/main/java/com/iluwatar/caching/package-info.java @@ -0,0 +1,20 @@ +/** + * 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; diff --git a/caching/src/test/java/com/iluwatar/caching/AppTest.java b/caching/src/test/java/com/iluwatar/caching/AppTest.java index 12b72d56a..a50b687c2 100644 --- a/caching/src/test/java/com/iluwatar/caching/AppTest.java +++ b/caching/src/test/java/com/iluwatar/caching/AppTest.java @@ -25,25 +25,21 @@ package com.iluwatar.caching; import org.junit.jupiter.api.Test; -import java.io.IOException; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; /** * 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. */ @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/caching/src/test/java/com/iluwatar/caching/CachingTest.java b/caching/src/test/java/com/iluwatar/caching/CachingTest.java index 8869a66ac..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 */ @@ -43,32 +43,30 @@ 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 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..a82876469 --- /dev/null +++ b/caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java @@ -0,0 +1,83 @@ +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.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