Date: Tue, 17 Aug 2021 22:05:01 +0300
Subject: [PATCH 03/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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 c459b92933cc87fc406fe09f8f20907a84b7d7c6 Mon Sep 17 00:00:00 2001
From: Victor Zalevskii
Date: Wed, 6 Oct 2021 15:34:04 +0300
Subject: [PATCH 13/13] Fixes according PR comments. Mainly Readme edits.
---
caching/README.md | 123 ++++++++++--------
caching/etc/caching.png | Bin 114847 -> 0 bytes
.../main/java/com/iluwatar/caching/App.java | 25 +++-
.../java/com/iluwatar/caching/CacheStore.java | 2 +-
.../java/com/iluwatar/caching/LruCache.java | 2 +-
5 files changed, 88 insertions(+), 64 deletions(-)
delete mode 100644 caching/etc/caching.png
diff --git a/caching/README.md b/caching/README.md
index bd6e05f77..9c4d11b9e 100644
--- a/caching/README.md
+++ b/caching/README.md
@@ -43,39 +43,29 @@ Wikipedia says:
**Programmatic Example**
Let's first look at the data layer of our application. The interesting classes are `UserAccount`
-which is a simple Java object containing the user account details, and `DbManager` which handles
-reading and writing of these objects to/from MongoDB database.
+which is a simple Java object containing the user account details, and `DbManager` interface which handles
+reading and writing of these objects to/from database.
```java
-@Setter
-@Getter
+@Data
@AllArgsConstructor
@ToString
+@EqualsAndHashCode
public class UserAccount {
private String userId;
private String userName;
private String additionalInfo;
}
-@Slf4j
-public final class DbManager {
+public interface DbManager {
- private static MongoClient mongoClient;
- private static MongoDatabase db;
-
- private DbManager() { /*...*/ }
-
- public static void createVirtualDb() { /*...*/ }
-
- public static void connect() throws ParseException { /*...*/ }
-
- public static UserAccount readFromDb(String userId) { /*...*/ }
-
- public static void writeToDb(UserAccount userAccount) { /*...*/ }
-
- public static void updateDb(UserAccount userAccount) { /*...*/ }
-
- public static void upsertDb(UserAccount userAccount) { /*...*/ }
+ void connect();
+ void disconnect();
+
+ UserAccount readFromDb(String userId);
+ UserAccount writeToDb(UserAccount userAccount);
+ UserAccount updateDb(UserAccount userAccount);
+ UserAccount upsertDb(UserAccount userAccount);
}
```
@@ -171,30 +161,43 @@ strategies.
@Slf4j
public class CacheStore {
+ private static final int CAPACITY = 3;
private static LruCache cache;
+ private final DbManager dbManager;
/* ... details omitted ... */
- public static UserAccount readThrough(String userId) {
+ public UserAccount readThrough(final String userId) {
if (cache.contains(userId)) {
- LOGGER.info("# Cache Hit!");
+ LOGGER.info("# Found in Cache!");
return cache.get(userId);
}
- LOGGER.info("# Cache Miss!");
- UserAccount userAccount = DbManager.readFromDb(userId);
+ LOGGER.info("# Not found in cache! Go to DB!!");
+ UserAccount userAccount = dbManager.readFromDb(userId);
cache.set(userId, userAccount);
return userAccount;
}
- public static void writeThrough(UserAccount userAccount) {
+ public void writeThrough(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
- DbManager.updateDb(userAccount);
+ dbManager.updateDb(userAccount);
} else {
- DbManager.writeToDb(userAccount);
+ dbManager.writeToDb(userAccount);
}
cache.set(userAccount.getUserId(), userAccount);
}
+ public void writeAround(final UserAccount userAccount) {
+ if (cache.contains(userAccount.getUserId())) {
+ dbManager.updateDb(userAccount);
+ // Cache data has been updated -- remove older
+ cache.invalidate(userAccount.getUserId());
+ // version from cache.
+ } else {
+ dbManager.writeToDb(userAccount);
+ }
+ }
+
public static void clearCache() {
if (cache != null) {
cache.clear();
@@ -225,34 +228,39 @@ class.
public final class AppManager {
private static CachingPolicy cachingPolicy;
+ private final DbManager dbManager;
+ private final CacheStore cacheStore;
private AppManager() {
}
- public static void initDb(boolean useMongoDb) { /* ... */ }
+ public void initDb() { /* ... */ }
public static void initCachingPolicy(CachingPolicy policy) { /* ... */ }
public static void initCacheCapacity(int capacity) { /* ... */ }
- public static UserAccount find(String userId) {
- if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) {
- return CacheStore.readThrough(userId);
+ public UserAccount find(final String userId) {
+ LOGGER.info("Trying to find {} in cache", userId);
+ if (cachingPolicy == CachingPolicy.THROUGH
+ || cachingPolicy == CachingPolicy.AROUND) {
+ return cacheStore.readThrough(userId);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
- return CacheStore.readThroughWithWriteBackPolicy(userId);
+ return cacheStore.readThroughWithWriteBackPolicy(userId);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
return findAside(userId);
}
return null;
}
- public static void save(UserAccount userAccount) {
+ public void save(final UserAccount userAccount) {
+ LOGGER.info("Save record!");
if (cachingPolicy == CachingPolicy.THROUGH) {
- CacheStore.writeThrough(userAccount);
+ cacheStore.writeThrough(userAccount);
} else if (cachingPolicy == CachingPolicy.AROUND) {
- CacheStore.writeAround(userAccount);
+ cacheStore.writeAround(userAccount);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
- CacheStore.writeBehind(userAccount);
+ cacheStore.writeBehind(userAccount);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
saveAside(userAccount);
}
@@ -272,24 +280,35 @@ Here is what we do in the main class of the application.
@Slf4j
public class App {
- public static void main(String[] args) {
- AppManager.initDb(false);
- AppManager.initCacheCapacity(3);
- var app = new App();
+ public static void main(final String[] args) {
+ boolean isDbMongo = isDbMongo(args);
+ if(isDbMongo){
+ LOGGER.info("Using the Mongo database engine to run the application.");
+ } else {
+ LOGGER.info("Using the 'in Memory' database to run the application.");
+ }
+ App app = new App(isDbMongo);
app.useReadAndWriteThroughStrategy();
+ String splitLine = "==============================================";
+ LOGGER.info(splitLine);
app.useReadThroughAndWriteAroundStrategy();
+ LOGGER.info(splitLine);
app.useReadThroughAndWriteBehindStrategy();
+ LOGGER.info(splitLine);
app.useCacheAsideStategy();
+ LOGGER.info(splitLine);
}
public void useReadAndWriteThroughStrategy() {
LOGGER.info("# CachingPolicy.THROUGH");
- AppManager.initCachingPolicy(CachingPolicy.THROUGH);
+ appManager.initCachingPolicy(CachingPolicy.THROUGH);
+
var userAccount1 = new UserAccount("001", "John", "He is a boy.");
- AppManager.save(userAccount1);
- LOGGER.info(AppManager.printCacheContent());
- AppManager.find("001");
- AppManager.find("001");
+
+ appManager.save(userAccount1);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("001");
+ appManager.find("001");
}
public void useReadThroughAndWriteAroundStrategy() { /* ... */ }
@@ -300,16 +319,6 @@ public class App {
}
```
-Finally, here is some of the console output from the program.
-
-```
-12:32:53.845 [main] INFO com.iluwatar.caching.App - # CachingPolicy.THROUGH
-12:32:53.900 [main] INFO com.iluwatar.caching.App -
---CACHE CONTENT--
-UserAccount(userId=001, userName=John, additionalInfo=He is a boy.)
-----
-```
-
## Class diagram

diff --git a/caching/etc/caching.png b/caching/etc/caching.png
deleted file mode 100644
index b6ed703ab8b7dfce5e5d82f097dac534828fc93a..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 114847
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
zcm)yLf0F?Zr%{kXPNN0Mq*~s*=A}=FwGBb-0{AF|KS{#&H$}R^+LMSfELdoV=>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
zm|gWeiNF4Rh%U<7^^sA`3UNB=el4CTb#R*YWI>(ck0&j4kNyi7{}X(@g1}OF8+2K>
z$@SwDz}?3*z)n1Ge^Owb)AE+0V@GiojRN!yf-J+;HtgasfB?1XYesjgf};8}=%AQ=
zdXpMGP}aee|7cqO`Sh=`GZ+XCa^8ibzNS=MqLxZMrxXQpbKrSLuUeR(0yke6xU`XtYOfvdPYi(k3w1yJ3*2w7yTE1o
z<8^yF^-}cfE^y{80miAqM0=jZY4!Zd@(NVSk~e&7HzS$pj~=;0`pruSlFI!P68FG8
zu`1_dHs4&NeJI+2YN6KCi-;j`xw2gAtx+Jg{xVAoJr3S%dwX+h`tN*o5j&&xBuAC%
z#_347^}8aSMaF=Jn9c>k
zqW#7f+wS7`a*h2CE|b>9*LMQWXS?_c`uh5Hb#+8UM7b&j(3yySzmt(nY0t~8?9VaV
z<9T;Sz0CJm;3qDl2I;4>-RbxBE+{W}#`^pFJ3BY}Vp+|pfvwW2Kb$6JGLW#fu|X|K
zgOqCAAD5?6AWX^4&Hd^X`jypW;d}@o=VJ`W;UlSMxVX5L`t8ul?V(f=e%G_HW1i=S
z;tA{a)ps1zjh?X1(2(WMyVJ?|*G?LIwg(H7pJRcUUe*p0L%{kFyhf+^u>Rs7R0Mu?
zfcBPx9H8S}9rsZ<*W#~nai4&dZ#dhay)o4VY@hqz^-%D*pFsvlNpPEbTj#40Z5L9J
z(vG*e^|$56Akg}bgY~?wtmQ_T@xPdlKojD(#Fv({9O-~^Eo~GRlgh$I+Q0}E4`Wvrf66zjDKf$Zhu0{OPG?=8ln5QaDS`r^$_LuLJ)bx-7P;24tp10nm
z=N%M+JkkoY-%&v%Lf96SVvAG}rPztZ*ZT>A+MI6!Hw-o+dVb
zpE=u5rLd*{GV^b#Aq7H+7t_(kV57_FHm~D;x#c_$9o;t2x=YDlsoyPtt4>Xy!SC_l
z{ML^MZTa~&o&zOMhya+Jc%sa?dKSQxr%Ym&TP9ZCMM=UvY<}7
zI0_mX+Ojs(@8~6|Lj`T$R;g!>6^Nva;hicC*6w^#s`1Z9!!26tWt{GVvVaAQqhX
zqDRnbfsdcrHwx|~=SH#LDC2T=0@8>_QziAl4*i}f>5Q1ux}J5nSM}J*bq>$)A$ET<
zNemMNTxUY}pwxUR4SS1$L3Xy76g!V|&<_^mvEP)0zYS8?l5dBIcu
zoU?uR9#riBKWi@FEA`~VR1lwhVlB3^);kN@$SnxnRu3#JBq2K)V*L>*1EnwhdG(h?
z_bR_kt)G!gyXK9?{TpCVL57-koPC*%`-d+*Vu5WHXpE05gQC`L85{>h;FD_K41e$S
zKK#p_BF*;fFWZSAAi>gvd(SXqx_UjYFQ7{Ezkhq&XKp>ugu8+h)FrRwJsj0nt8xG?FAPHUi
z(DZZ)X>OnSfKkNhC{`^1(q2sgalVoB`e3iXv)+f{Dxt$k>&A(?x
zGw(s)XN8s3(FUI^AsAO$Z7qGU*`K)I>h|2erA*9q?-K53%!4^8^ZA8Py1N?$Ty#r!OE)4V-5^rZCEY1VNJ@7rDJ>=44c|ncz2CjhdB5|AA6yEo
z8F!3(j4{1piQj?Za44nOh{=HqNKhJbgApV~HL@s-TFqKXwP*kiutzowJ0KH`E^BR5
zE9@AHU-X=|(C7Z{0}omwc75
zP*89H;EtL_uX7>-C>9P5x7i?Gg4@}S78}fCdvJxFKyvcAzfClSM@A+R^5hlPZSlN(
z$*T>n>4T20FFTSt+cD*0v%yRF`Be76PBbv%*Pmo9MGU
z8+P-wbFDw#kmT_73l$(edA31Zeh=VQ%}+^52%ua1UQ8U08+CGA62lB;&L8Lim>93_
z=l|_BXc7B|8y~4YnAMbJS26E{Hi}-2eu({%tOL%002tO+VqpcBakVp;cEN(P$Ge09
zeURk8z1V#CGj~AS2}~(Qac|Z^>3gIBH)N5zr9AqBEjAh%psww^sQ^S%z^QBW_9Ahi
zl8oFT#=+@i2aFMau$S3SV1j~OOcxc6`V&Wz=hcOl-ut-f)FM@3QO>Ko$v&Vv`p9v|
zfc$dwphN+r*A@pdeL1M-$jTM9AZK}Z5}Tmb3Lx2q`ODF-vBgM8SwBj9i|aR4zd&tUd%k7whwm$nhlc>pE4efpFMD20s%!l!
z!`^2U4Y@uFrfuthJr3&IfTOe_0Qx0f3k2nYylY(V{+cz@VK5>xfMClZ29Pf9*S{fP*g
z;D4d50CjY9^kjWyV^ew(!RwgB?_Av4DjfcBpd=#+d;frEp!D|gI9?xuLnUrU8U&-1K2csjtkIWr2&ArbpeDgZ4Y%yvKr9Nx?Q!3hd-
zk!g)6J{YJJ0*YN8xWnoIZ!sC!1Tx#q0-UE9e_Vn_myZ;Z*?m#XS0;x_9#ou;lm1Hj
zbPU{sI8z%S^e(jb@((F*C*UBpcNtWfd`NWDXs{eWgV)@~WAnqxk^BN+219-$0I+{AqDaK%)bz1^H;l&;*ifhjc|Z
zK=VKheb;kk<(5SqP8W*(vZO^Cl?c%B4WhvaBAYZ;#$7HKef+mdUJmQ&V#mo=#@p@5
z4^&mKScTtkfDx8|PjW8i2U-%C-o&&Uvu}mWGJT9&&(n+UpHWne3l~%kr_W6njHC**
z{K5UaG6n|peP(ouFpARrzbu5R{M}wFywCLu+CRclyQO}-gsvP+yb@mPi`}@nJO=0v
zJLQWPo*?!DQz2|l)SxF%p8|M~B6SEJL}y+B0U9bQz27Pyu4{3zk?JD
z(z;nivoxKQHmletVITcU!!}f*i>1jjoht)Kkb0N%yVT@s>S`M>N`S|H98(u6#awN7#!Sm$e0$ve+PgjnTIB$4#9r9Ax^r8pM$q;eW{JP%`J|_QHp?(eR}WukRlog*
zA$oBOQoNMdtzn&tkzB5gcU!dOOTd*$f&`$~ocZ#1-jj$t9!1ICea&JKi8tm2$iBeZ
zNv2?F3G;2{x2iRBKz`fWls+6L5f-8(x})`&f%&QwUv^4-jYpo*omNyzJV
z(_aDdV>L_U)xn=4l#u~kIgylI0ClD6MTW(Wdi+BTe~>v|1r2p4EBfd{Z|eiUM!8LO
zu~BE!g7(~nF+Jm3IGO7_sa`Xp72
z4~4>)XjCw3RhfJ??t(?a`}PKahJnc(=CgBixGegF@Nc)qsh~Z70jYq1fK+ZS6zm1NCVE{FG_C^mcFW-L1B-N@=#f9kuVF6?WxzVO2Z9t!hRyO(-
zMrX@GvFg8cp2OUR`Zz^GEbtM+rx(&5KYT#u1!Rt#bWr`%*<0m?fx<>@z27vPJE9;n
z0;U;!>_>^`<9SRCc4;>F03!z@Lm--K(pmmd1aM7SdQU!8rLzKV7GYfjAk7A(v=?WO
zTCvJpYwxR~*|0dR_M3jj>bzq?kq~Q8_lz2Qq*kOXG1??Qm+6Q44ND!!YN80j)Dgf;
z^I&_*6%MY>c*Fs_TdaS5%I7&6w1-x1rY*F}<0VD7j3*E}i7nyW0DmXD`IM}5Y|a*O
z;}uWCk8dF;LQuFAY|G29UudQH-zhgatu05b3@{56E4$qLlVN`7)puFrh-V7hageiN
z@Xq`W`v1v@&q~Ft3U?x}|AA0FxGqko)bPKP)+miOx|9hrOS#{ae`(J_sQr7EmM<(Z
z@rodH@MK(EYT=^3m=*fXGCZl!<^FgEI21y|LvX6DudaTpu%dAH&wPPQR-2DZPiqFe
zFZc&UB@KUx_Ol8YY=o%*FQO+JudaMHADDhMl02O_xQuv_Hia3AiOk-;BjZXVe&m(x
zs;A0%kmJPZ8d~^3#S(RKHki-8%*4%?d-G0i3PlwN5%?3Bg}(F=iz7nYr@)#8S~rK>
z0d;>V;2>cnBcT{gx)BeKFLc;4!D~^i#E=nP&t)Q?g#M?Z%B)$pflRymvc5G{3kYGt
ziWy?eZc*BF2Tw1>IkJ(0W$ruLo_(p;tG$7UI1L4FAfz0=ZfQ~D&~%F*FoalP3S_(9
z^v;`Wh?8W5z~j`g_cp9<`i`;f|K!^SjEd;YCb^f#Y~lHhOm#_Xs}tDEP|TsWUJUM;PWnRj*Dp};xgMvP|L8jGyIsaOu#8M
ze{~NMBG_s(2su94m*WE|8u{O-Y0PTH2jzOzL;AUl@kUr!XFt-I9Xo2%w9nMXG{v%c
zqo#?3J>BS>dd8SP5KPU@&tu;nMw6vK7J1N|rzT=zk~{wKEH7Cc6O`=LFK=tH43
zs)6Sr-8hZH4goqkxy9W~8PFfmS)yCVn}FD=kY#guDL(N}XpIjMe$UdlQz$cYOTgI{
zx?`##q@q_HX@T6+sbz6UHjKq3nKoXo7XbM4OTU4Dp`jeM^{&A#?|CjUWW1+5zGq*C
zp-Byy0%uC6gx|4ERiYRc@+4$hQ~q1uZ+9)5UbP|PT{ud{uE6RFFs#
z5+ql@^!k$;**&cOgbbG7DMV0Em~K>ckeL4ee019?D
zH9x<(+HQ!qQk--q4tAjjQ0Ggqn`Pa;TgYF~em$TV!#>@g-4Iy@Gfj
z_$m=&V2mw;Q9LHEz+6rgF6TTqzhS8w9wuF?!M)|nw-N1e&U7Z{q!uq;7`_6h!P9IS
zkj#K&SJ=hoYi59dU>)Y$-h>R?Zc@m{Kl#&CQmub)F%v<;gr)H)`FD!57(XEgz^V@i
zkP06ydd7CAdB*)-?Fn%hT2`;Bb2F{@XS0DuC?qTWy^u>84a}hxspbr;v14;hF(yyM
zKOgqrBsDN=xDSM1++2tWi>2VIZXsQ1<@rePd5w)$
zh;u|ZPK4N0Xe63Do)m3VSlpkmu9w5=j4TnCF7?a`c!u_m;A)gT-~VMYk|~7?^BIAE
z>9N$nh$$Kj0{=QvjJ^xhPBm}m4n~w3Z_IfnenfSultR0M4S+yLvVMr$zkm0_o`p#k
z!NPE@iZ+>41YbOCpjqO(e*kPZF0@k1|0yBE{0E82DJVja30zjYk;~rx8{_?75b7D5
z&3Abu5YMw6){KE5gkir=m=paD!DiqH3k9f=Rc-+lbiX9mi*
z<*;S4xd<`qyIP!REX>5#94X2r)PMHN#eqL*bguj~PCe*zuuyXO>C5Oxnh
zZS;8wEiW(6%E~gvA^$hFF9%iJjAWVq&=8t*`%DU=JfcfdMHNF7d3kjj2JG@~E;mp*
zfq&B1cWR6KbJ}UD;!u&=N^1_3f9~r99-UIOOgl(S9qsL%jAG@*e??yG!TwS>l&6EI
zWK0OU61=vpw+Wf?$Aih!adD;qKxPrrzcwA%p{!AguCgHHfQg0K>a`)rCgp-*L!7aw
zqZgSq5loRjLmxA_w~yCYq$28=5)EwvRux74{Bwgb9fQc|FxK#eW=;JMh;e>wg!qHg
zgv5-+x*fN*fi%?I?cB`L=Pzh&muhlXoEOD@RBKh6h%AUmu2p5TsRjNyj;Y=TG#x^h
z+6P>Lp=4lA{Cc|5$o+V|tKtbvGxB{IAn9uNKT!SF0WmllV1yY45eD8fA5MT0IrOb$
z&kGyui6F$oR~gh4>0U1afTGICcJ{mZA&42oFX}IV(7u=AQ-c>UNAUA&X`j0knO^u0
z99jDuEHMC>U17f&AY6R7#iSy%n!uu0C_DSr9jE${$Ku(LE>pb
zN8>vqs0{Q4Z3}v00x&0_Wpfm|UU2QEdm!@MAo^8dbz-5Tw+kcd0!GhaPb7(uTvg2w
z-6uBeo;A$2p8q89K?N8Of%De1G#DEQUFLQG|B9Fg)0kJnyU#3&^yk9`*MToD#kIr%
zfCK=~j}IJuKLjBuD6&nN)Jh2Q_cv7)uphFw6QL-7%yzXid!t0pt1%l~4>pN0Ta+4mZ`ySHUC@Pgzv>n$pFlsfo*D0D&U`uQpLyt`@U+p=2qNX!T$o<?W
zslaHX^oivc`$a{!^9}<(r}ZSxqWcFUQjsS>!4)os^+-TkD)fbqP&r^jY;8n2!ubA0
z=yuCs1PrJZN}*fMDjL}C!mcP_(W~n;
zv}jaqzVo+)YWvX}`LAkU`F$x*)dZwjwO$kMd{Q$S$mGYb%6u?e#49#~LtHcRsw2E+
z0rVgHtkQ#k*XXllAlhXyXyJUzKnJ6jf!T5Q68bEhgKN>a9G!^VVj*p+gqE9T4fp5}wir)B{$v{{pzo0K@@$KhRcO
zU!5XY#y_QZ^YG}O>;{wBX}N-F>`Z^9p8r9+6w$QXsct*DTw_0MU%SJ?-LaX1thA9|nuTLNEh2U_RO6D@9G-pWwXD)myTOn6IWowgE-{fCpb<|0{}V%jxr
ztk=Kv`9ot@KX#N_!P`0OTFlrV{D{~}(T>YtHgx*?$ML~|#bV2Cv&T8so0fN{#VQ2~
z9A<;8Cuoo|NH2{|4{ZW6Ix>87EOa}9_hQrGaIDA_=nfOc|1$A_(Cw?!ADjyDel?0<
zi9Yl(!XYSCJ0D~fL1TVZKq6-Jh{Lqs7Yvxf&wX(|5Uc~xYHSE$Um4Bd8t6*-F
zU=>KR6Sst3!*72J%M3u{wC6k9igjU7f;(DwmXXn>wD`%L20Q?RYS^7jO#(Gt_(y>e
zN_Mshu}5)?zgt%jPn0$Tm*FjyGt486zCQb4v**9e@X3O135*nObn{=`=jzm%F~-#F
z`@+@MdtKwc815_yxA|xNk&3VWfL3nTSFp%E>4R{bDu8`CEU^p&IdwcuU$yN@-#>0rW)%&)|{n(5d
z=Bt7|htr}7*a1c>Q=plHmwUqA#|Qz_MTQfW&F^oOMtvXeetTLKC@zJ{qX&VG+oQ
zAe2l3Um;*_Si6bgR+z&C!Bx3Qi#v%S(h
z%75K44Y&$mD5*`WgZrGVV>wfWVmAaz%INXxg8N|P*q+E;E9ha+dGB
zCDml!(Xf(Ceg&;^)flm&DXZ`OnI%f0cL_rjfaQn)u7H)}u6cDD1WGB$nI_cJ+6q-;{+X;HE@
zXtO6%D-pn&2Ht<}YM4b1hzwHk-41xSwzv0wcECn*L|J+S%ULvwpU!~CahFTJtQvTP
zT2;hnJ^`1|x+!w1Qet?TL+XG?cng9E
zqZT`4$xnNTb`#8{bDjLokn?~MoFo}e7uLy~JwmAA#_>kTvcqPrrh*NFohT{V6S=VF
zPYz%-#)&fc;J9wuya2?vl^g7)$T|ok38eIK7z6ujnz{NHYgi}+-dt>3nAti&Xf8Rca!I3iQxOIu2g(I
z!8N*AVNazG-#$aeJ~`~+;C~BW?_!JEohySq#bM6fVdn5?27wJ9eCrqe1&N`A-7+bC#o-_S(VrqC)~0k8|(CoG5Ap$$+TZTJ%|dH%+R-q+P`EGXWV6
zQPR4u1>-Cd+TTKE>PLYHx0OwJ{3mL)nPW5brmuMHzO&QpI>t)!UTf;nQ5BIQRsxjs
z{&w-+w8wRSflH8M>63(aNeO8mW#DhB7V5Z{5D?GhV3LjI8@6x?NofN&
zK3P+1K#@YT;1dO02ABDon_gVBD)7#55U_3?oX@+$Rc4ftPJi}J2js+WuY!(zv>?cui4Y9@#YJ}bigJbO*TIM*
zPxu7{jAkGN#^Ob58g|NJ3f?6Q7&U*k-m~;Sw_bHyR#UTaD0NZz?Iy)~SLKW6tfWZn
zuuLHWI4Ouc)XP%EHT_j%w8{o2sVxU@tj}fL^x`~3E
z({Ft8%Jk8fea|#J>n&+%#yzXU*wimxhDasqwrW*n4VR2ukXxDUgz^F0nTP0&<~R!M{Vk|vPYMqXj^
zMyRcA1nV8d>Q}pjK&n>9)j6uXd<5Oy(zF4LxDR*1NcUK36pPm&mZwq-0hNP5lEA;w6+Tya7QOh92m?d2xj`U~GW1)g
zUGD$f(K>>Ipx8Ghcv0xQK^2qHZr5i*`qT-N%*?|DuL}Bb6`BrlY{X+xUuzY^<335$
z@9;&`fWG_i>fvUl>$YsH<+ZP~;*`?k$Vu
zQWe?jpQ#))}2uucB88A4Z^Iba;&;<-IpM)S!{L_oMG6Juk(aYi#+E<=ct&9T0sf~OI8
z;CJ8zo=_8R8tirATQ*A{>|dOMoXiZ&-Uc@mdr>`-lqsgx-+Rp$Tf}cxqsc11b;#xm
zERb1~A-e9R1yDg{`o$PuT=>Yg0o^YJq2X?ui>GwHj~`hlpGqTuz(=PuW5$D6QXr;GT>2Lu>uNoSO_3b(X
zp{voQ)r7Pg-;XQ*K}~z;aV8sMo_;e8&8x`H?4eS`e(_WX${*UMGZvfsvN3W7ZeMGz
z^SDU{&w~4J!)I-vN?uoiO_yf4F2`98|F6|=Y;3Gb)ww&)c$J<;ziA(@2xFP_a3P9F
z1^$VB2Q%gwHMVmJL>p^sTvp$u1X^#^&=Riu1s365V9Wj4Hq7|2CytWm%^hYpH_I^P
z2%}mrLdB|WL_=C;%k`YxR_E%@+kDhZ)PuT0;~8i|0-v>?le>H~GUkKaM}_**QY23z
zFloOCvR|`~3Jml)N<3h%wVUUqv;6XAB;LUz>CN@zO!Zc8_g(yA<9YZ_>l-T`X=Ao-
z;`k5Dm6E~Vc5~V^_m>~ZiyOXiD(UpbFiAeOhFw8lT0DwF5HPP)J~Tf3%CwLno|
zPMf689DKafh!E{?(`1o9W%58R$Uw_#%vO3f254de7>>KssJ#3-R69k%eAo@hzPQFD
zARv9YH=%o4@4Bx)l)}Zq!Qsei_{~>3miqI_?e)1q^E)-~Z!cS6(3vuC)kdQ~B)8E$
z#XRezP9kpd3s|Cxk>VT(Hhi3j60)_k^J{y1XpzNV4Rd^Ey`-|zWw3@>mluU^S?Y7J
zQhAQUw}rU@K94i_b?oqp!$3ZR^QPn`@9qh`c@W%2qwVq&`;M;|L#-FnElrDuOh$;3_^
z!E7aUp<>pH1g`cQt5htby-%O^GzFUuyJfqSpjSg0ynF3OKk{Q|*FY*b-y3;NdD(-_
zK&?@<(k~X10$FjA=KE+>R%OHa{yXk%euc|dt}AfjDZ3s5PRt0-Wx`Bo=&K+5flb0u#kat{p~T58y>rwy5HV}+S;xJj&pca
zl8uzcTIr0L4ZRDQSJOA6pRFVtwmG2;dc6IP-(LZo(fO6sbkRq9nxks8YFuV^h#BtiZ;x=U
zQjJ%7y3w>8xHW1`1<%)Wo$7a9JLkpa`ZG#Ep?+tg=R;9d7OU-6Wt+0kcAqlqqBS8@_tHzfky@>G`x!I+C-Z?7HEX$dEM6?%!XAg!u4FJ}N^a`{(Y5$yR0qT&Ex}d9k+JA`C+Qo6t2Iy|9T{GgSl&7=Q$8DXbfCnuR-Ca$@Jo
z&-Um(R~qyh={Dk1hDwO@IaQDgdRjzokzMn$nqF=*+_!~$bd^4JJUFii&5N&1O@&O5
zW0Z}&$@LHI#K^kEb>XZCdsqV`#5@2sFIU|#Y-Y4(==D0cx^r*{3(ujzs;4F1@bXH-
zpv!WBFJV-o%22Uvg^v(+Mo!hy*Egr}axk~fz>fK=kPug+tB?Mtq~v74qnw(Xo0yom
zoiV?iStX&G5Fk6-)d4(6;_o_n?-@4t_JFu^RX%k^D;UJYWd@!)nH`<(iGqnh7hoH<
zoRm}tv@GW*6GBV9F-z^Nq2A$zLH=nc3r9JamTWyDz~a8TUo2qQw-({$1Ra(I$$$
z3KdWk>9q>EjORW+JiIrgYrXgW*41?x&O+P_m%K%IMhu?^F$RP)Jdd`|!4Lp+konXd
zmFZ|HC}95@p>BR-O5-DP`}>E=?d`Tk`AsLs$P46}4h%rc_7?%#%th~OE>4UsWP(ZW
zjr1up?N45l_lx%nfiGxY*%`910CTi?-^WH2l4k&g6(s$}!m>X$qP4X}vh+HgUq39Y
z10au7L69eNUo0DpyG#g8%vttkIPlg&LR<~)qc`Y7HF9k<-@bk3Vf4J-=brq7gJoJO
zR%NIQX%Q*;XA=t>?A$|CJ@T;yPU2HLz^vajky4lw{9sRUI|B-2XWP_^v+|y$7z=
zQ&nl-!=a*7pWt%@tCwzm&y!8PJk9~!EX+>^@-Wb5H+OfbC^a;(F@KoXc$XyyfZ}<1
zd#-aqM=N=!@6OkH&5V4tG5wi~$Ig=&8#~fWiaH`Nzg(B^a<)crzEK$PV=d0^Ohb7*
zVe$@tmk{N(wvv~M1RYHjpnmmq@2NBrY;;BxA%E^G!p@GNkUootw>k2hbYH>So7?1L
zbv0(-zASfW}awF8|fRNJmV}d=hDm$gw}z>*f$C9|8JBn9lMo`0S?MWbgJDpm=!K
zB?&!tI;(^U;8sz+q8M}%yz6b?tcPIOIo^Th5vMi0+s4NB?bxn6OqdHr5ow1=kw5c@
z*3g}w|5Hyn&gO7FR?{St28!Xg;|7-xy@vq=j5GxA-tim^uxNZBoPpzitZlsaQ;AUt
zERPtJ3o6EjjrDw%`$f~!Re}8`W=?vkpaiDdKm+iKNY^L2_w=OnNx!GR(6zW92Qxl3
z={sDt&T{(slm0W-m-k)_8qkN7G`F+UHI#BGlYjmk5RZeY$~D=v%Kb^*05u)y_NmA-
za-wDwh*cYV246M4yZ7BCM;=6(duMNchh9xLtL5(ISP{I#1TKgE^Nsv@oa~8w4a@}18xs6ukj&Q
ziI&EVYtn~m838r@@5$3A+usl0o&JavzWp+>^5pAK+elSe2%zTy>ZnnV#~G#8VGWs#
zRa1gkJ&CHh!IJ2*^aHnW3-5DZEX7QZ_0$X
z)b-VMo)2+9k>8zrxx_7BP0?=Di6ZY4GyPY?n9r$(XDk
zj>Oh+Y%_P36&}vv(e9T55&3j=YAr23gL|@DxBVG!sZ~fiKZL4sYI@c1_xCmjAwc}J
zUlj61NUEOd)H!FTT0k2WKbWm>i9DkD;fH-{^S9_P%P)1TGo(o@g0LffYm(Vv8*tD%{k
zWkRFMXM>R)(dLdG_Q1`0h@HSFM3XKLT@9^Hq;%cLzkjPNqo0emQ_bZpr!0BW%RT$!
z^EHO^fZzKAK-Mu|XK$WRZ_*d|Nig7ZoC%5W?OPmSvtJXUyzaco5E0@8@Yc(M-9)}+
z%oSMcqev{vPl!dr1>7~aF$xgL%#&hLaF@UV)C|>h$XcVYf!io=@~>5d;>azpOlZRz
z{FVZ-d-Q;wc>i4LC(%IF@*|QVcEGJh=Kr-h_TvEcp##ZsAw?LU5QaxON!;j^+$5CN
zgAo@G;(*CBfVn-CRLGNIK1vs{wB!Ivq?nA1=69#xLGAVuV4&NRT7XCYt*t$1^}^O{
zW5j-_S1LM0Bnz^#_y+rn<7>jj!Pu1NIZYG_!yq;V2iKbR90aZn2oL6{Li>ZI^SfRq
zGTR2D?t{lu>XrFhRVaj~aNpBy&`zSI?Ss3$PYoNV
zzSnjiPVOzXCQZS}GW=HOBriBsLwhmHQ1RBVC+TuWK`Wg*SGSauyam#Fl6Wkj#UO44%suEej>gF
z#=?>t>&&8_Mq@@GYTC$v8V;`-Fg-(zR}JoGL$yKB4_!s4c-Pa}WaHT~$$KzvW0`%6Fbc($U?Q78_gZdz)p>@0=)#x|Y
zIX@$7&Z+waXuoh6U4HEMR?NDDT>Co{h_wF!7Jp#hc>D`kL{0GXjrn^TmN4Tf`cy~3
zb&sgzbTsyv9lLmz9oR>{`q%bPV5?^Tuk1s3m<>UxadbU?pFgu>?Qx-L`^p+Yi
zqVH|zCEgXB49g=={Ep#wF`TU@qEXNNMg1o{!JixC<;vO2)#A554s8qEj6a!2PyBTE
zSZ2C*SJXmQv5YnlK^MlGzgq3rFCMBD_RmS7rvy*(n%W+)!uydo^mX8__itAjGCYp~
z{Y0VSDyGESbneblG;g@KE-vl;>lcSLrL;9S{Y)Q3Qdc=up#5bLZ|24qQfc=yM~4H~
zWa+P=LF*-xiJ+fN!J6GmAt;4O4A9RO~1
zwNaBhz4GBjJ&qo-e#C+yoR`@B|J)jEE*yd!T3aBNgascXL>G
zW!%r7ri|3>8`Zt-d}eY6s%^r>wh^GWv-yI%A<};wlgDAn_QesN2aK6bL@DsDm^M
z+A;IGC$jYG3_l;jtiqo#OIydZ+Z=X;0@;<*Y{{)6QMG8X@=E5~Cdz%4%8geIZYcF6
z01H828O(zez-7E>kKt^jKw;osM+r=3n#Q2tk&0RWdZ2CXDr0+;YD>WMoy6}iQrPkS
ztMwEqSH}`XrmbK5-`-u896R?|WI%FSXjl(lsg&Tq*P1@mkTdQ(^NMnRqigkN_+%k3
zDdQCf0|WcBxvC!s73`jFEWBuE8APe$cl5oM?<{oHb#GcOB!7}bMM=eNpzpPc)Gy!j
z#>(A)>Sz$zQzhU!iV+?$?2Xo_{2eW4MaYlO4~WrxwDeS_*_GfZ*9LuvDawRF>^?{+
zH6R%5GUcnQO`e!Uz_Ly;s1lWU!Ovg2ybJYmuH|TDPd$sC}2WD(mvgA
z#qFj`lCu98T>7>-3iNQCJ}h?aFK+M4+?T%1RELJ3c-81NuJf*)?Rtft0x_78t9pel
zA0H(pS~5B!+FLa0Kyd^$MSXqx_QkCU%Y!+v=@;)F1r1~@`|-N{>g`w~oNLbi%oSOq
z?vsgy0;6Q{EvST4u-o;_<{c<(c0y7XnhlWP4<_tI&FJBijqsns1{Nh(I3VVv46)%z
z!yuefL;gm4tG_V;_k;)&yd(4bChQvsHsD4HkjH%)KPLeSH^3T^3^9?xZ2-1mu$B5+lRyhWQefI;p{!cSvoHF{;^ztu*-
z3$Kq62Jq4TTaAxf?(Yu1UBL4UOv)NsSj@f0PG3q`zWF@(64u804`n|8OhBB?qF?3+4Y8#^3ucrv{|5`6J<+R61|kE^E4v9xA;s^>RS
zoRugv4(lHPE&$fKryn*z$a4JApVN4B=EZ1AuS%Pd+9;P~V2hYDx{eo~>rXlMpGPs}
zviLO5<9vhdhZ@`sp;S4rB@A0-$k!n>1Ti-7GScK<@7aq{^?
zhLrew8vEXo5G_K;Igu_Etu_Bd(XOG<{DYbSnf
zLu4`40u#3mf7R%Ki933DB&|DzDN`lcDvTAE?xlyS*kY&k>Uc!7-%FiQ{{X3-rmUKV
zwpRrluge}1J~I|V$=uhjrn32U0tc@-Aa&h+Iy$sW)9=?68B-u1-{O(14y7jdYg}XZ
z1alrC*0Ivt4jV(xsZ*Rs^IgokS`PYDa${J2o6=iJo{Tsx*QjsDb&IpjaTiaJBrADL
zSY>Gp@zP6;3sx|-j2@vg{}$KCnLYhc?_UdVFH^!gm%}JW_dU;|QYuH4`=O#I#s0O#
zh>ra%a+W__@H-Ym_x39!M3_Hm~&$x8FSb
z2q90f0$(s>g8_^nmj3VyrnO+8g@7R3vGT^@!Axe_iEe*!L|*3k?)LQo!gV{3BX%`2
z99a>maS`<&5JrkVRO-95GBjA+v8g`{bDNx;AC++W$H2g9yaxXPZoTPWNaqa|Kn^@?
z`8A*b8Cu$x5ppn;R#Zf^w;!on^hSO{&ChQ;1NX)E#OA#%h3F
zPMeoWO2ff{?_=Q8jLCllKsxZxBs|*NXTfW2`z00XRSl!VkDs;!+YpuJ29KA%2Koy*
z8Bw5L`_{wn+=PJ{0tO!m7o2pj{}Z;0DD%%vO%6fTuz0-k?X626$lgW|C?4&w)noRj
z1+S7u_peR`!6c=BA>338lXZ3lDO0$9R)C%zvFP+Q(?{zwQPq)O5w2o69lby9)$a@SH=l1w#Me
zkly+)RWj=JvfVlJfkh4VERoBti4d>{fMNP+z5#R^UD=QE@ev9XY>`0w*;+G`1&$y)
z5P~0cM~qxb64#T>My8rDDl8A-oI{DNE@YvS4oe8QXFtkGz4e}xJ4rc!Nx*c>Jb*{cxdg{JQcYn4tNBR5&r8h3FxhlN|)DwXWd{OtlJ*7_|17~}s^N#VbGt4>H
zf5%2!ovd}wMg8F-;Bjtdoz5ENJfI`T>k+k?i0!}o^;y}|9$X2n!3vif_*@vD-rVc}
zQN$AFX@n!y9O$MrG#;8j9WdUL(0tS}|8r5GefSwwYtfGhCEwZ8K#<{lB50!ye3
zdU$HUU+nBZUEiDR4@vk}%VIr7`
zn~(8fu&EsH47!G1K8v+@f|mrV0VUj#BwNN(pRG)Bri)0{DSGHLZ=|x_ndm%&SjG9!
z15xDh0)HJRuBSUcM3>P^ozeI}$}C9xmQHQcJs3SqPt%?~MZs8BW<}py91$mP+?6l#
zzdNmnh^POW#$SWpLm_bj4!SJ+{D-Swu|G226{Gl!K2gtpv*=HUFprIk8}yWx#DWG4
zG^WZ0Zz>o>&H(lGdxd|w!D2vmRTYkYedTTtu68V>s@n3uAO!EfAc2Fa5t&+X{U^Ta
z%R~d0(yxUTZ1
z-?z@85d7eGwi^0`-d@{RrpuSSNIM3G#Ux&w()IXJ8U>T#RZv;g=-NX^d#dESpqXMd
zid(a1IZ94ci(ZMaBW^p>DFi%1Jx~1gDA(?!*3~|rv}tG}e5MMoWJW`zPP?14*U}V+
z3bdgkrWV^mtb(j{fK{LW-huscTf8YmUKgOULTz9(5+qchJ{eQ%*Zy(GBD_@blX=|G
z`gp5C(t-9U2Ym*wAa-#NomxsKi+0k^4VIFab-kibjL*_(*IP(KaRw_IoSQRUKvmE`
zj)@u4*wAos@m-AEtU33ng>+D>o>aKCAuG!^`Qzhd9Q}7&K5p3h`ouRr{vRB)0HjUM
zFM9U9!XSLU>Txz2Ww`-Ki3*G~wQC5*s2lIg5U*;*u)<8@qStWw5p#~l;xcv^CIYFg@vyiU+5`qA+ps|-No
zTqF@Q*DvEu)#MNOb(g0E;k{nhP6-I&%r=~%HfE^GAklMjapjLG4DE>1z4xBSK5`&zxpvJ_D0(xx
zP`(^~AgfY|y7O>2Zk=O6#8Y$g0>F23M*#3k0P2)MoTp?m!0aLj&dtCyNw#lDulpyj
zF~op&);wo6iOp*a=JFyi#C5u!4F$c{dMf0;9n-FR^#_87l9}e8Zt_=n@Kd;iO7Q>%
zj;ph|)ADlW9pR!6yx<|3>m(w%bbh?VXMA&YsD=Y5;0S+p1de>&BNfajfzD)tl$4e4
zoLzrC&PoTR(cjfok)5jwnqOXo45)HAR^MhGAxL0;>*b6Dk)seSsIq3_Xwp!-uk(Md
z=$~_IlJ9S^pcV5l-zzC3EkTHQDUr|AmaxfBbSy
zL|c>+h$G>`^T4Uo3_iT4kj=mEBn#|Icgc|uI@-WT!assMNxRGbb*?NEr8WwNVi!ci
zps``+@m_A{7UN8?$cV7=x^@8~i;qn>CVLY!zkq{ssU87E@}8dZe=nWG(8ZPEK*IS<
zHnaf$Us$;DzO|jMua#$rZ9y!+3SWd~SPd^`l
znIMS>OoS@8+8kD35U$lsJGlKprp3wiGv12-MB;8GXaWp&7P2g4TRGM14MUCbg;MI2
zj6s$hy27(8QA}7e4s5aG;rD+sp(Ko6a|3}>zqW+G783NR0~OzeV}L=!rCf`HiO-$!
zTJ7f_9ur67$kLawF&&yDUSgj^^su8B3>pe32nd0%Bz%1-#CKkDI)Y?crN~~E`OuCF
zCIsxlb+mzr4md-E=)XWa?lAVyj$13G|I>hz&w~a$6cIGw^#qd3z~4r;YD$8uR5*%I
zi@C41Z~kj_s{#>AY59!P@1WE}H)pG6S_05JjlcE;kyo@&x2YZ(XX1g0!0pr~nQg@X!#%>f=F{zOr0>XXgT3j74C+?P(&WV@*
z!3|@y9Hd=aDR84>lx#&^{u8yI22cJG#RM;XFPJEsb(n$JIkBd0)pd}=T@l|RvMCsc
z3Edq1eui-1OF!1cuofGXi7DaMNc#C1IAXxZ-prx@TcF@)gvQsHu01QHZqk$(Ywi$`
z|36Kampw3E)*cuy%(y5xb~mG0QJM<1BuL(PZ7>C-0oB#9fJWA78}lBcn6&RpV%x+7
z(WmY}DQ7>>EC$f*h|d^~sRDY@
zQ#j<`k>bM4-5_;OXG2@X{V{3IdQyn3iT=Q{Hba3mFzOskI#*nllMzr^P^C9*W
zTyrFJ0N!aZ)ZCtwPvr49+d;3t>CeZpg0x3k=(xC?VHmky1Y#u=)#6Ur2p4+#k3C~$
zjK~4=1$rwL<$q!yc1P4Km=Ags#nbhNk%kebXrrzCJLCW9Lt_dk0vUY@wyO%Mv=AHx
zUOV_kwcFUB*1mw*mR|Gbxb$WeH$US#*Wi<+gvW$x#3I
z>2S;1f8|JUO_bUE-;V~zt16(G;#G=Yu;q+vz^lXsArhrT+uVV8&HNCrbNva^s7`Zt
zlyuBLhor1zcEV*9{?j>Mh@zK?)I?bA&cW%{8@8UW$Mb6+>&<%157c`fcreBw@?$c=+#1loc2U
z(i)5BPY4vQGn%JROgnF|V4ww#Y0r&NqzeWyv2A0
zEG0xnKDDXa-+=5FMXU$h^WYj>eOfX}dv@jq2Tee=1I#o4MGR>9@4*Q1hN=ijS0TGa1
zAc&xJN_R*o(kUU`A|aiEfRu<5Qqn03(j_g@Ak7{NyyNwG_IrKTKKeNDL|8HZbB;O2
z_{9j#^u|WNgNRg?S6B)7vYlpIE^tb?(Hr#oep+TphPsH?bhAX^vY2Q`N!Rvx;!0f2
zi$H$%d!$BqHhOjU^jQLqY3=vyC@;gfjl1<7S{S5d49jPiIM!Z_HA!;k<4VgN)6vxy
z%F+~m`ZNh9^=zC1u8-SeJcufYnC`7s9l2alMo&VbwJ&kKlRrIc(AZiOiiZ-ZES=6+
zzgu7br`~gsgH;^q1WrRUCnu~J-8bH;pX8|1%r*5_
zd4-tjFJKF;h`4x50Xv%+&oZceEmw1)7H{r4FnBWV+uiay&=vAa8`S@JRUP+d;OQ@3ilJs1*pZS&)jR1$$NEk7zqV03I?@TU4J#TgZ;t!2@XNmsZyYU0y=wxO?B#pUu
zyVvm!^%BY@ps>)RZ(Lx6D*8f4d(2(5CBpjTXvG$aaI%z;&6b_I1qm&W?tL&`Tz!
z*RA_I)T{*CF;8|k>LCIn{yNa2mIE5Rj1&<8gCISB*0EY2(1VS+hg?&2>bI5)S1f%v
zcr1rV#;csgV^HuSzc+tNcY|`94Fko`ofZE)r9C|bI!2jZb4&O;#I#wtmin`85wlSWn$3P`{)dzSK*`%Dyd0VozT&X6;4tOG@w$iU1e`PsL(}?&tbFL9
z@@b%u&aNaIceP24jm7FP{1h-2;9j5psBh;mUJIib?;Hq|@=}2nCkOS*Hxkn_n~Sw?
z7_Yra(bbN*I39NKxMqpM;hDj+zB&R8iYe+F+1H0!MbhuUV5aXE)26hv66I24pC??f
zDH`e65q?`(qtnQR-CSx3O=+(QW@cG=FH?N%orl%lX9!JSkNew}?*kP{TU(n5(@Ei^
za8hRnOKJ%)lN!yxRCQq3++Wl}k6g@9$}P;A+%umReG789A6&~9+b0Q9?1w}|I$hnj3=%Z-7S)3vPk?QEAhT(0Ckazjnakbg~Q+)xW#WH7Y@4HWo|7BGFeE;T|m
zU^%iYak)WXUk
z*1EYt$CjB&XZjB?sZ|DSfg+L*xZbvJR12gFb&}$6!Ey*$(C8iTjaAr_2}pcwfOd{=
zAHqibMXM{4%CxJr@Vkd*T6>4&67P*My$|tgF^_H#hnWrn8v(UWK|V1RwHqj_xGRC*
zj}PXQuDrs<#y{DRo(GXixlzFf!l7^9#+N<(CTd~FcSG|A*ZJw04f}8o
zDYLIPq2Juo^Nww8eTD}3L{%3gD`)Ci-pZvh8?z%>FmILRo9UQSDwVQ1gq0CAKGPR0
zr;&%e%^eXUb|($VKAN2PB4=v9&}#x!CKzWtzgKhav5j6zpv|n6^yrB44fd2lcg$Cz
zVPpJtpVoGQk;rZxhD2>!+k*go&&g-~mg;(HQocz=#q|26y1>Kh7+>S*?lA1f!E^9Km#SD68d;2CFgS&Vcq8V~Xa*V6*F`aU
zXc|6VpZ0?+`!kILZ?HrAGZm5{jUYu;LS=MuPh8Gg%IdlWvPr7d!vS5EJhw2ZCAH*EYBEpY7S{}8uBF*ey
zpO`Q$DJ9>Kba8qQ#plIl=xQz9qK+ehrdGOR5$oF&p_m9q=fgeq!*g1E$;MAl^QXt&
zxPpuf{01V4bO-CO@3I(P)6tqg5(M538v#+(8d6@z(Z~>TRtg{y?kbUfUS{2;>7?&>-INIBp$TBe_X(?Jrb7#06Ov7K?XUYL@p)!6)ubA*_*+Y7AIje8Rn%
zFSru=sv%*NxQ~HD^K8`}VRF_mFfwu!8gYfc8rrJ83a)${8IJD#kgNZIzI}Y5g3aQt
zs@O;u1+=I%{GQBlaou=6xCUKg3g;tI{19C_**{@
z68-_4;SWsl$)R20o!ItQJQsaR+Mg*J3d#tarbAQcq_j^56NK0@;)hAeE`^*8n0KMw
zeSPk^uYH<&5@$%cmgj)sEBWXWTx1dTfTiWt?u%){ncG1RO!8dOSi4A)!o&uPjt<3@
zlni(7eBjl4JNoI{Do?-hU7@S4U;8zd(z5yFJ@7j5HTc~3hyv7gbyolt8rElC>V`lK
zS6l3P)M7yGCIyPsINy;{Syt(y{48U5HIj^H#rn6N8m%VdL+DIU#Zw)X-IzHSWV&$gr
z6}2$=*H51*1Y%KWqQg^?f8!+6cRr1#|49UY#3hk~2>qC@{`_~#VKn`p5`pud_4#c;
zRv5C{!X`k)WjU?OFq}!TuKY$0A;`h>F?DHVOPsE7Y69Mit?%>=nRAF_t|FIjT7^0h
zfXS?vCtnf;gVX@22W{}K3i1Ka^91=s-Zawkk=7=(!dht<0cc+aFRc^=5XivIi@~l!
z1Ai_Z@oSl;>hbp{M{6e)m!;-6#U`ltL?4lm+{Hi~7nRapjIJtYIp6;J`?b8oUO+V)
z((|vW4Bq~dY|Zn)F6LT{11Y!F(8Tf4uVBsc68WTq(fiOwL9&fCkohED7P~fo;46?t
zr?z}SnszrAz?>VsWQvuuugoMp3
zt*F8WO9`3Q3utKxovs0!5&%{1E70kD6E1iEy_)8ZJQ;01)$k_oFqzR>z9kmZP2_W4
z%Jz;s)UEl5byF3XfZ+E00fDwcC_O#CHGucmXV5c#sc
zp|i8Cc9FZxbnyHnXB+G#3?yP=V@6g^Us2;-kib!9j|NHfYOR-c!zl{ve^D4=e!~R+
zEa6J+db>)`-;xD%>J_L(ZzHj3OnhwD{h!}nt2x4~>$$Gv`-B7@+1A>HAD-OFRl$NX
z>FvXBf~}jHKmpw#*Ueo0D4zBG`pW3hVX*VCJmc$$7hyn*e}Rb(mfk{&>;F^6pkV-@
zJn+c}s_<|vymB}#)w@A#GFYxz-W3#tYx?sx9;ZOh8uS>YvvWVaHoOFOj71)Y4?cfB
z*MxJrm{64xVeKnOQQ$<6X?5OU5#k#FS{(?yAe#sXKlXnx&+^sZ!XN7_sS(1m@`-6p
z(cpv^nhX&Y=rGQ;4sc1ur-u;1&hwagaYujsN1}y1KKgApi*S3vfOKg8Va71
z+wT!;@Bu84?p^LpDt*krc>!thUvr&Q1^3QLVtP@jZMS-;@lCn~97rY7
z_+(TjqhwsmTz(E&zwu6$qoYquN_rHueJ~YMm>9Q;)-5s~Rfgq(e}vfE-&4Fb%2dh5
zFM}s@8{d?XGD|Syj`pMxQ
ztuiIaS8w-VhxA1t8HA|$>)64y<{lhIb!9#Rmw|{HgAxIcUZ5?oBX%y|#PJjGU0+0s
z&Ud~@R+z7=yZJD?dV2_O={?~7@$s$oP8mPN@y-(Vhgw;5j{Vl3oC_|38jVHC_ng>$
zjH^D+2RnKGV~N|A1ANNV3Lae8!}6s=VE)`~WY@jLxiZ%2H|xcPAcaYrKELGOT<^@L
z$|xXD%6j&{08yK;Ig>(w`$_B37L9n%{JR#5#m6ej`U{0>u>Yn@a*dw>RUkHx&P0%C
zLkmO5>5&tNF*tA%`#!p#xq&v|_wUN^HD2qH1m;Bd7YUUe
zdTi2Od?_zSOk^cghrwTja$*cMd}%2=vh>l0XIYiOAiDqtQrn%iW(=N~!>$#J_UT9tC%~>_Q^~3P5_<+hz
z`jr#AZEiXGY6zhU6k0!188&C7yK`pipac%1)plQ>#(dPBysIvm>SoM7B>c8#Rc~}4
zyMO5!Uh`Ul1>n#xBO_Chv4W(FToRZ9-6|1WeDS%dC6o~`vfaw0+_5_dG
zX8*)jwJP@T1ERhKiF?IcZy1)VjhsMVg+pZzVfrZ+IZ!R-~em7=fKV?W-u
zbieWG8IiIHrtp4azCYsg>uhgd%t9!U1Fy#GoZUQSWps6mcs5Bt7a*FkEA37%ecN-y
zV)kDvyS5Dyl4&}@zCztRNIsD07^@gsgVdv26Xt|8djT`PPH)sdHNwtsNt)zrkuE&B^Fqu9vOxS?x@P8N_|f<>`D
zRsf8IFLalbjEs^(gK+1LGaKlfAZE{@IXgSAn)=_qZ8uvU-~)P0*qm-|7U?t4`iwa`
z%5=idef3IU>MPJ)js9Np+|@oh!nw_}Xq0`&wB|3oprxjD1_PG$
zb<=KIl$zZ9EcuHM%$$l0ER`OD=QYLPvNuYEP6NiK>ZTHF?Hv~rs5KY>ZZl#x!6yHt`
zBPuyadefdBV|3}$&yYG4VcgQ10{Y`Ofv`NWEgt^NBC6
zth{w@X*xmgMKx3CwnT2RK1tG@3~wHGM(GW7P>~5yI|!
z5r8Co{5ZDMngb&zQOK_itQya3cLPLh(CP;
z=tg<-X@|)>c>U0v`YdId%0PiT6I8COa_z&?Jt-RIh>IRA95SXqWCZe$rQOhj?g9>F
z@RBSt$N&Ei1z<>%uIFgp8m{kuVs9$}?tfH*B+EK75hlW;lCoL?wnSEUPSHdjcPIL~
z`KKrYEpOWfWiUX`7gtmK6X2zbDKA0&d%eSXRWE3r=8K@hk*$tuwqO?D^vW^Dk3Lsh
zWUu`e@bmkIho98@dRSa&3R!U7oL?OG6nQF4Mlv1eo^YaFC%@$6QX#URe+*nW7XN_H
z542eyBk8|%eAq$fm5&q=;cG3`1QbNsUw*}7;QyRYpHzS5)g01MIy@AJ$Jh*dZ3^|>
zcH&c{?<1dwMz{K)cbftiHWK$FqQ>7=KmctMo3yUHG#n|@?8{T~!5kyMj;nJ%tH66g
z5WN2AuRni9@_bJv!ry-wND3C?$)`o(lGAD7*Ipy>5NztBUX;mmHPWGT0YiRlxKSOq9jK|&9SXbVJP=nsmrKxR^BrFPXvgX_*
zN=R2EBBBYkS7B-fQ>!om>NiEO2Bb8%aS9{uATtJo$e^&W
zi8`tH0IALO_U_j910=9j|5`FDKprxnH(G;%(ZpB!L0rpYYm@?t+|OUDs_q<@YF4|v
zuJJrn27_
zP^WQn^wc1H{ErFtXJWO1@t2+51T14lu99H@fiHcr8q2{RTL&!nQ4QaZ^6`=hB-b*O
z#E8$8XY{lJn&g?R(BL?Kd3_(xDgMz1wwXW5r6<5xjR>GnK4mbc3N`p`E%mcw2O@cX
zhfhw+yKpFmZ$Yx+bj>?5FRvYTT51VD(P->(N}kfIer)}ds!S3e3rhI{xCM7%w$Ra9
zvz$85SP}@S0B$re7#sTN&a;wVbg6!g_wp%_)4m*Fh&5783ytB1UpR(n%puqQDN$<`
z>IFbk9xtG(f2*FtWOnf9#HUV%zZSc}KXyYkHG1^`pt6wm{@Qa1e{~OGe+af65UVOI&h0An~-^ij;$L>qLi#js9JfUGIMdu5PVfRl=S$NUMYhXa)HM
z?HBbjGn|2w!ZF7NiP@8^X8i~1G`RZb{E3~%Ty5x%15998eYofCX+Xe*-TCj+iaJZI
zpLuN=_~;-M^b2r!ME`-8;${?H(`Bs?H({2|+a7yd(<+LMy#p)-KnBhx)#8!IFox*c
zc3WRWMz*dlSs-U^M1zAI;wp{+^OAR!iiIKYa&J04a@?Jf&(4t$rWHo&`&1%I+L3X=
z`*A8+QDU}#lW}9?O0egr!!
zf|o&x;0KhEQd8mj=B*#DWNZ`-!4GPLkvk~e5BDXI1)wa6IX=OAVk@0*5PQVAdMx{^
z6($dX!JSQwcKCN7O#@?FR8isY72*$&V!D|1(Ouv=bTK{e0ds+)M>iSeBB$BqFs-V
z;)*1{#&(CIVLEwKSayWX_Gu{PZp+1?8-S(3n5dY02>~7azt^hl(d7HN6r0xdM3EW*
z>=E${A#Tixc*)>nI0xXUF+3gAIzD+kb>Y%|d|4`*YJ2a%?`z~pE+|^c!t(OV7iC1W
zTflL?fkogS@(G|sjzR)$lvn@b--^i7vK|7;Lr^i%7J-`w~LGiHAp3P96(!VPpe-4N*Pao7$MseQYGuEq5
z9`GG44y-hMOjN~mKG0-v!sExcLY4A2YRbxd!+f59y5m%Tf;Iv1nN`&eER?${
z?7z%h>Hul9^JkDe0YAuo(Wx3dqz*8#jMD*B=Hj&qn!EZ>A<+Ron0dSUPI@~B;1=?d
z+nnRcEQc==?GAnopeUG9w17a!q#3SLq(g|aAQFGV5f8YJ#GhsYFerq!YgNXQpt82d
zlrLN3r8^DxP+d>Q|F~hI6&O-={ucJ9waCi_9qaQ7i28vuHCIEft=1P-^?KcEgW3uZ
z{Y&8t^|hx5_TiOlA=EuQ^VpQLCxumL%tnU^4T%HdjrZ3kX|A8)^?@E9_Mgcw|AcC=
zh1X2p(N~e%5WI7SSOP-N4<|f?BnR@Tvy>>TM!N#qu@da9(gDL4e9+vu+aE1!VGb)?
zjptEekX$W9(v>!1Y`J3J)QKFmD17A9ipx>B%-~&OK_3aJ;n6T8eYfHMkE)~5T0(#)Ztd4N;Yh`$E;hD17vNlJmlP9!3!pZOJ1{{D
zCBSVcqGfnp>~Ht*6|xXXDimk$Wga{wJo#*k-@jv()R-I{iZ)a3xMK3H;K$8>ii)*_
zPp9b8;=yhKpY^!#L_LOPb@&^SP?7&xVcfkklRWxB`N2LgLy&@?jHaYM)(OE6SwFfmq0xs