From 9db8d5d4aa27c094a2e9d174f6b4058fac1adb1d Mon Sep 17 00:00:00 2001 From: Parth Patil Date: Thu, 22 Jan 2026 20:40:54 -0500 Subject: [PATCH 1/2] Changes made by Agent https://smith.langchain.com/public/2be16652-708e-41ca-adc1-2edd5bbdca93/r --- .../persistence/repository/DataGenerator.java | 864 +++++++++--------- .../persistence/repository/EMFManager.java | Bin 5571 -> 5913 bytes .../algorithm/RecommenderSelector.java | 329 +++---- .../servlet/TrainingSynchronizer.java | 13 +- .../webui/servlet/AbstractUIServlet.java | 20 +- .../registryclient/rest/TrackingFilter.java | 15 +- .../registryclient/util/RESTClient.java | 161 +++- 7 files changed, 721 insertions(+), 681 deletions(-) diff --git a/services/tools.descartes.teastore.persistence/src/main/java/tools/descartes/teastore/persistence/repository/DataGenerator.java b/services/tools.descartes.teastore.persistence/src/main/java/tools/descartes/teastore/persistence/repository/DataGenerator.java index 97a9573b5..bf7545ce1 100644 --- a/services/tools.descartes.teastore.persistence/src/main/java/tools/descartes/teastore/persistence/repository/DataGenerator.java +++ b/services/tools.descartes.teastore.persistence/src/main/java/tools/descartes/teastore/persistence/repository/DataGenerator.java @@ -1,430 +1,434 @@ -/** - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package tools.descartes.teastore.persistence.repository; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.Random; -import java.util.stream.IntStream; - -import jakarta.persistence.EntityManager; -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; - -import org.eclipse.persistence.sessions.server.ServerSession; -import org.eclipse.persistence.tools.schemaframework.SchemaManager; -import org.mindrot.jbcrypt.BCrypt; - -import tools.descartes.teastore.persistence.domain.CategoryRepository; -import tools.descartes.teastore.persistence.domain.OrderItemRepository; -import tools.descartes.teastore.persistence.domain.OrderRepository; -import tools.descartes.teastore.persistence.domain.PersistenceCategory; -import tools.descartes.teastore.persistence.domain.PersistenceOrder; -import tools.descartes.teastore.persistence.domain.ProductRepository; -import tools.descartes.teastore.persistence.domain.UserRepository; -import tools.descartes.teastore.registryclient.loadbalancers.ServiceLoadBalancer; -import tools.descartes.teastore.registryclient.util.RESTClient; -import tools.descartes.teastore.entities.Category; -import tools.descartes.teastore.entities.Order; -import tools.descartes.teastore.entities.OrderItem; -import tools.descartes.teastore.entities.Product; -import tools.descartes.teastore.entities.User; - -/** - * Class for generating data in the database. - * - * @author Joakim von Kistowski - * - */ -public final class DataGenerator { - - /** - * Status code for maintenance mode. - */ - public static final int MAINTENANCE_STATUS_CODE = 503; - - /** - * Default category count for small database. - */ - public static final int SMALL_DB_CATEGORIES = 5; - /** - * Default product count per category for small database. - */ - public static final int SMALL_DB_PRODUCTS_PER_CATEGORY = 100; - /** - * Default user count for small database. - */ - public static final int SMALL_DB_USERS = 100; - /** - * Default max order per user for small database. - */ - public static final int SMALL_DB_MAX_ORDERS_PER_USER = 5; - - /** - * Default category count for tiny database. - */ - public static final int TINY_DB_CATEGORIES = 2; - /** - * Default product count per category for tiny database. - */ - public static final int TINY_DB_PRODUCTS_PER_CATEGORY = 20; - /** - * Default user count for tiny database. - */ - public static final int TINY_DB_USERS = 5; - /** - * Default max order per user for tiny database. - */ - public static final int TINY_DB_MAX_ORDERS_PER_USER = 2; - - private Random random = new Random(5); - - private static final String PASSWORD = "password"; - private static final String[] CATEGORYNAMES = { "Black Tea", "Green Tea", "Herbal Tea", "Rooibos", "White Tea", - "Tea Cups", "Tea Pots", "Filters", "Infusers" }; - private static final String[] CATEGORYDESCRIPTIONS = { "Pure black tea and blends", "From China and Japan", - "Helps when you feel sick", "In many variations", "If green tea doesn't agree with you", - "Cups and glasses", "Classy and useful", "For extremely fine grained tea", - "No metal for green tea" }; - - private static final String[][] PRODUCTNAMES = { - { "Earl Grey (loose)", "Assam (loose)", "Darjeeling (loose)", "Frisian Black Tee (loose)", - "Anatolian Assam (loose)", "Earl Grey (20 bags)", "Assam (20 bags)", "Darjeeling (20 bags)", - "Ceylon (loose)", "Ceylon (20 bags)", "House blend (20 bags)", "Assam with Ginger (20 bags)"}, - { "Sencha (loose)", "Sencha (15 bags)", "Sencha (25 bags)", "Earl Grey Green (loose)", - "Earl Grey Green (15 bags)", "Earl Grey Green (25 bags)", "Matcha 30 g", "Matcha 50 g", - "Matcha 100 g", "Gunpowder Tea (loose)", "Gunpowder Tea (15 bags)", "Gunpowder Tea (25 bags)" }, - { "Camomile (loose)", "Camomile (15 bags)", "Peepermint (loose)", "Peppermint (15 bags)", - "Peppermint (15 bags)", "Sweet Mint (loose)", "Sweet Mint (15 bags)", "Sweet Mint (25 bags)", - "Lemongrass (loose)", "Lemongrass (20 bags)", "Chai Mate (15 bags)", "Chai Mate (25 bags)", - "Stomach Soothing Tea (15 bags)", "Headache Soothing Tea (15 bags)" }, - { "Rooibos Pure (loose)", "Rooibos Pure (20 bags)", "Rooibos Orange (loose)", "Rooibos Orange (20 bags)", - "Rooibos Coconut (loose)", "Rooibos Coconut (20 bags)", "Rooibos Vanilla (loose)", - "Rooibos Pure (20 bags)", "Rooibos Ginger (loose)", "Rooibos Pure (20 bags)", - "Rooibos Grapefruit (loose)", "Rooibos Pure (20 bags)" }, - { "White Tea (loose)", "White Tea (15 bags)", "White Tea (25 bags)", "White Chai (loose)", - "White Chai (15 bags)", "White Chai (25 bags)", "Pai Mu Tan White (loose)", - "Pai Mu Tan White (15 bags)", "Pai Mu Tan White (25 bags)", "White Apricot (loose)", - "White Apricot (15 bags)", "White Apricot (25 bags)" }, - { "Ceramic Cup White", "Ceramic Cup Blue", "Ceramic Cup Green", "Ceramic Cup Black", - "Percelain Cup White", "Porcelain Cup with Flowers", "Poercelain Cup with Dog Picture", - "Small Glass Cup", "Large Glass Cup", "Small Glass Cup with Glass Infuser", - "Large Glass Cup with Glass Infuser", "Small Glass Cup with Plastic Infuser", - "Large Glass Cup with Plastic Infuser" }, - { "Porcelain Teapot White, 2 Cups", "Porcelain Teapot White, 5 Cups", - "Porcelain Teapot with Flowers, 2 Cups", "Porcelain Teapot with Flowers, 5 Cups", - "Persian Teapot, 3 Cups", "Large Teapot with Glass Infuser, 7 Cups", - "Small Teapot with Glass Infuser, 3 Cups", "Medium Teapot with Glass Infuser, 5 Cups", - "Large Glass Teapot with Steel Infuser, 7 Cups", "Small Glass Teapot with Steel Infuser, 3 Cups", - "Medium Glass Teapot with Steel Infuser, 5 Cups", "Glass Teapot Warmer" }, - { "Filters with Drawstring, 100 pcs.", "Filters with Drawstring, 250 pcs.", - "Filters with Drawstring, 500 pcs.", "Tea Sack, 50 pcs.", "Tea Sack, 125 pcs.", - "Tea Sack, 500 pcs.", "Reusible Cotton Tea Sack, 10 pcs.", "Reusible Cotton Tea Sack, 35 pcs.", - "Reusable Cotton Tea Sack, 50 pcs.", "Pyramid-shaped Tea Filter, 10 pcs.", - "Pyramid-shaped Tea Filter, 25 pcs.", "Mr. Tea Filter, 10 pcs." }, - { "Medium Mesh Ball with Chain", "Medium Snap Mesh Ball", "Large Ball with Chain", - "Small Mesh Ball with Chain", "Small Snap Mesh Ball", "Large Snap Mesh Ball", - "Medium Silicone Ball Infuser", "Small Silicone Ball Infuser", - "Large Silicone Ball Infuser", "Small Mesh Ball with Panda Look", "Heart-shaped Infuser" } }; - - private static final String[] FIRSTNAMES = {"James", "John", "Robert", "Michael", "William", "David", - "Richard", "Charles", "Jospeph", "Thomas", "Christopher", "Daniel", "Paul", "Mark", "Donald", - "George", "Kenneth", "Steven", "Edward", "Brian", "Ronald", "Anthony", "Kevin", "Jason", - "Matthew", "Gary", "Timothy", "Jose", "Larry", "Jeffrey", "Frank", "Scott", "Eric", "Stephen", - "Andrew", "Raymond", "Gregory", "Joshua", "Jerry", "Dennis", "Walter", "Patrick", "Peter", - "Mary", "Patricia", "Barbara", "Elizabeth", "Jennifer", "Maria", "Susan", "Margaret", "Dorothy", - "Lisa", "Nancy", "Karen", "Betty", "Helen", "Sandra", "Donna", "Carol", "Ruth", "Sharon", - "Michelle", "Laura", "Sarah", "Kimberly", "Deborah", "Jessica", "Shirley", "Cynthia"}; - private static final String[] LASTNAMES = {"Smith", "Johnson", "Williams", "Jones", "Brown", "Davis", - "Miller", "Wilson", "Moorse", "Taylor", "Anderson", "Thomas", "Jackson", "White", "Harris", - "Martin", "Thompson", "Garcia", "Martinez", "Robinson", "Clark", "Rodriguez", "Lewis", "Lee", - "Walker", "Hall", "Allen", "Young", "Hernandez", "King", "Wright", "Lopez", "Hill", "Scoot"}; - - private static final int MAX_ITEMS_PER_ORDER = 10; - private static final double PREFFERED_CATEGORY_CHANCE = 0.825; - - - /** - * The data generator singleton. - */ - public static final DataGenerator GENERATOR = new DataGenerator(); - - private boolean maintenanceMode = false; - - private DataGenerator() { - - } - - /** - * Checks if the database is empty. - * - * @return True if the database is empty. - */ - public boolean isDatabaseEmpty() { - // every other entity requires a valid category or user - return (CategoryRepository.REPOSITORY.getAllEntities(-1, 1).size() == 0 - && UserRepository.REPOSITORY.getAllEntities(-1, 1).size() == 0); - } - - /** - * Generates data for the database. Uses a fixed random seed. - * - * @param categories - * Number of categories. - * @param productsPerCategory - * Number of products per category. - * @param users - * Number of users. Password is always "password". - * @param maxOrdersPerUser - * Maximum order per user. - */ - public void generateDatabaseContent(int categories, int productsPerCategory, - int users, int maxOrdersPerUser) { - setGenerationFinishedFlag(false); - CacheManager.MANAGER.clearAllCaches(); - random = new Random(5); - generateCategories(categories); - generateProducts(productsPerCategory); - generateUsers(users); - generateOrders(maxOrdersPerUser, productsPerCategory); - setGenerationFinishedFlag(true); - CacheManager.MANAGER.clearAllCaches(); - } - - private void generateCategories(int categories) { - for (int i = 0; i < categories; i++) { - Category category = new Category(); - if (i < CATEGORYDESCRIPTIONS.length) { - category.setDescription(CATEGORYDESCRIPTIONS[i]); - } else { - int version = i / CATEGORYDESCRIPTIONS.length; - category.setDescription(CATEGORYDESCRIPTIONS[i % CATEGORYDESCRIPTIONS.length] + ", v" + version); - } - if (i < CATEGORYNAMES.length) { - category.setName(CATEGORYNAMES[i]); - } else { - int version = i / CATEGORYNAMES.length; - category.setName(CATEGORYNAMES[i % CATEGORYNAMES.length] + ", v" + version); - } - CategoryRepository.REPOSITORY.createEntity(category); - } - } - - private void generateProducts(int productsPerCategory) { - int categoryIndex = 0; - for (PersistenceCategory category : CategoryRepository.REPOSITORY.getAllEntities()) { - int productTypeIndex = categoryIndex % PRODUCTNAMES.length; - for (int i = 0; i < productsPerCategory; i++) { - int productIndex = i % PRODUCTNAMES[productTypeIndex].length; - int version = i / PRODUCTNAMES[productTypeIndex].length; - Product product = new Product(); - if (version == 0) { - product.setName(PRODUCTNAMES[productTypeIndex][productIndex]); - } else { - product.setName(PRODUCTNAMES[productTypeIndex][productIndex] + ", v" + version); - } - product.setDescription( - "Great " + category.getName() + ": " + PRODUCTNAMES[productTypeIndex][productIndex]); - product.setListPriceInCents(95 + random.nextInt(12000)); - product.setCategoryId(category.getId()); - ProductRepository.REPOSITORY.createEntity(product); - } - categoryIndex++; - } - } - - private void generateUsers(int users) { - IntStream.range(0, users).parallel().forEach(i -> { - User user = new User(); - user.setUserName("user" + i); - user.setEmail("user" + i + "@teastore.com"); - user.setRealName(FIRSTNAMES[random.nextInt(FIRSTNAMES.length)] - + " " + LASTNAMES[random.nextInt(LASTNAMES.length)]); - user.setPassword(BCrypt.hashpw(PASSWORD, BCrypt.gensalt(6))); - UserRepository.REPOSITORY.createEntity(user); - }); - } - - private void generateOrders(int maxOrdersPerUser, int productsPerCategory) { - UserRepository.REPOSITORY.getAllEntities().parallelStream().forEach(user -> { - for (int i = 0; i < random.nextInt(maxOrdersPerUser + 1); i++) { - Order order = new Order(); - order.setAddressName(user.getRealName()); - String eastWest = " East "; - if (random.nextDouble() > 0.5) { - eastWest = " West "; - } - String northSouth = " North"; - if (random.nextDouble() > 0.5) { - northSouth = " South"; - } - order.setAddress1(random.nextInt(9000) + eastWest + random.nextInt(9000) + northSouth); - order.setAddress2("District " + random.nextInt(500) + ", Utopia, " + (10000 + random.nextInt(40000))); - order.setCreditCardCompany("MasterCard"); - if (random.nextDouble() > 0.5) { - order.setCreditCardCompany("Visa"); - } - order.setCreditCardExpiryDate(LocalDate.ofYearDay(LocalDateTime.now().getYear() - + 1 + random.nextInt(10), 1 + random.nextInt(363)).format(DateTimeFormatter.ISO_LOCAL_DATE)); - order.setTime(LocalDateTime.of(LocalDateTime.now().getYear() - random.nextInt(10), - 1 + random.nextInt(10), 1 + random.nextInt(24), random.nextInt(23), random.nextInt(59)) - .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); - order.setUserId(user.getId()); - order.setCreditCardNumber(fourDigits() + " " + fourDigits() + " " + fourDigits() + " " + fourDigits()); - long orderId = OrderRepository.REPOSITORY.createEntity(order); - PersistenceOrder createdOrder = OrderRepository.REPOSITORY.getEntity(orderId); - long price = 0; - List categories = CategoryRepository.REPOSITORY.getAllEntities(); - Category preferred = categories.get(random.nextInt(categories.size())); - for (int j = 0; j < 1 + random.nextInt(MAX_ITEMS_PER_ORDER); j++) { - OrderItem item = generateOrderItem(createdOrder, preferred, productsPerCategory); - price += item.getQuantity() * item.getUnitPriceInCents(); - OrderItemRepository.REPOSITORY.createEntity(item); - } - createdOrder.setTotalPriceInCents(price); - OrderRepository.REPOSITORY.updateEntity(orderId, createdOrder); - } - }); - } - - //Order and preferred category must have a valid id! - private OrderItem generateOrderItem(Order order, Category preferred, int productsPerCategory) { - OrderItem item = new OrderItem(); - item.setOrderId(order.getId()); - item.setQuantity(random.nextInt(7)); - Category itemCategory = preferred; - if (random.nextDouble() > PREFFERED_CATEGORY_CHANCE) { - List categories = CategoryRepository.REPOSITORY.getAllEntities(); - itemCategory = categories.get(random.nextInt(categories.size())); - } - Product product = ProductRepository.REPOSITORY.getAllEntities( - itemCategory.getId(), random.nextInt(productsPerCategory), 1).get(0); - item.setProductId(product.getId()); - item.setUnitPriceInCents(product.getListPriceInCents()); - return item; - } - - private String fourDigits() { - return String.valueOf(1000 + random.nextInt(8999)); - } - - /** - * Drops database and recreates all tables.
- * Attention: Does not reset foreign persistence contexts. - * Best practice is to call CacheManager.MANAGER.resetAllEMFs() after dropping and then recreating the DB. - */ - public void dropAndCreateTables() { - CacheManager.MANAGER.clearLocalCacheOnly(); - ServerSession session = CategoryRepository.REPOSITORY.getEM().unwrap(ServerSession.class); - SchemaManager schemaManager = new SchemaManager(session); - schemaManager.replaceDefaultTables(true, true); - CacheManager.MANAGER.clearLocalCacheOnly(); - CacheManager.MANAGER.resetLocalEMF(); - setGenerationFinishedFlag(false); - CacheManager.MANAGER.clearAllCaches(); - } - - private void setGenerationFinishedFlag(boolean flag) { - EntityManager em = CategoryRepository.REPOSITORY.getEM(); - try { - em.getTransaction().begin(); - List entities = - em.createQuery("SELECT u FROM " - + DatabaseManagementEntity.class.getName() - + " u", DatabaseManagementEntity.class) - .getResultList(); - if (entities == null || entities.isEmpty()) { - DatabaseManagementEntity entity = new DatabaseManagementEntity(); - entity.setFinishedGenerating(flag); - em.persist(entity); - } else { - DatabaseManagementEntity entity = entities.get(0); - entity.setFinishedGenerating(flag); - } - em.getTransaction().commit(); - } finally { - em.close(); - } - } - - /** - * Returns true if the database has finished generating. - * False if it is currently generating. - * @return False if the database is generating. - */ - public boolean getGenerationFinishedFlag() { - if (isMaintenanceMode()) { - return false; - } - boolean finishedGenerating = false; - EntityManager em = CategoryRepository.REPOSITORY.getEM(); - try { - List entities = - em.createQuery("SELECT u FROM " - + DatabaseManagementEntity.class.getName() - + " u", DatabaseManagementEntity.class) - .getResultList(); - if (entities != null && !entities.isEmpty()) { - finishedGenerating = entities.get(0).isFinishedGenerating(); - } - } finally { - em.close(); - } - return finishedGenerating; - } - - /** - * Returns if the current persistence is in maintenance mode. - * Will return 503 on pretty much every external call in this mode. - * @return True if in maintenance, false otherwise. - */ - public boolean isMaintenanceMode() { - return maintenanceMode; - } - - /** - * Put the current persistence into maintenance mode. - * Will return 503 on pretty much every external call in this mode. - * @param maintenanceMode The maintenance flag. - */ - public synchronized void setMaintenanceModeInternal(boolean maintenanceMode) { - this.maintenanceMode = maintenanceMode; - } - - /** - * Puts all persistences into maintenance mode. - * Will return 503 on pretty much every external call once in this mode. - * @param maintenanceMode The maintenance flag. - */ - public void setMaintenanceModeGlobal(boolean maintenanceMode) { - setMaintenanceModeInternal(maintenanceMode); - List rs = ServiceLoadBalancer.multicastRESTToOtherServiceInstances( - "generatedb", String.class, client -> setMaintenanceModeExternal(client, maintenanceMode)); - rs.forEach(r -> { - if (r != null) { - r.bufferEntity(); - r.close(); - } - }); - } - - private Response setMaintenanceModeExternal(RESTClient client, final Boolean maintenanceMode) { - Response r = client.getEndpointTarget().path("maintenance") - .request(MediaType.TEXT_PLAIN).post(Entity.entity(String.valueOf(maintenanceMode), MediaType.TEXT_PLAIN)); - return r; - } -} +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tools.descartes.teastore.persistence.repository; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +import jakarta.persistence.EntityManager; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.eclipse.persistence.sessions.server.ServerSession; +import org.eclipse.persistence.tools.schemaframework.SchemaManager; +import org.mindrot.jbcrypt.BCrypt; + +import tools.descartes.teastore.persistence.domain.CategoryRepository; +import tools.descartes.teastore.persistence.domain.OrderItemRepository; +import tools.descartes.teastore.persistence.domain.OrderRepository; +import tools.descartes.teastore.persistence.domain.PersistenceCategory; +import tools.descartes.teastore.persistence.domain.PersistenceOrder; +import tools.descartes.teastore.persistence.domain.ProductRepository; +import tools.descartes.teastore.persistence.domain.UserRepository; +import tools.descartes.teastore.registryclient.loadbalancers.ServiceLoadBalancer; +import tools.descartes.teastore.registryclient.util.RESTClient; +import tools.descartes.teastore.entities.Category; +import tools.descartes.teastore.entities.Order; +import tools.descartes.teastore.entities.OrderItem; +import tools.descartes.teastore.entities.Product; +import tools.descartes.teastore.entities.User; + +/** + * Class for generating data in the database. + * + * @author Joakim von Kistowski + * + */ +public final class DataGenerator { + + /** + * Status code for maintenance mode. + */ + public static final int MAINTENANCE_STATUS_CODE = 503; + + /** + * Default category count for small database. + */ + public static final int SMALL_DB_CATEGORIES = 5; + /** + * Default product count per category for small database. + */ + public static final int SMALL_DB_PRODUCTS_PER_CATEGORY = 100; + /** + * Default user count for small database. + */ + public static final int SMALL_DB_USERS = 100; + /** + * Default max order per user for small database. + */ + public static final int SMALL_DB_MAX_ORDERS_PER_USER = 5; + + /** + * Default category count for tiny database. + */ + public static final int TINY_DB_CATEGORIES = 2; + /** + * Default product count per category for tiny database. + */ + public static final int TINY_DB_PRODUCTS_PER_CATEGORY = 20; + /** + * Default user count for tiny database. + */ + public static final int TINY_DB_USERS = 5; + /** + * Default max order per user for tiny database. + */ + public static final int TINY_DB_MAX_ORDERS_PER_USER = 2; + + private Random random = new Random(5); + + private static final String PASSWORD = "password"; + private static final String[] CATEGORYNAMES = { "Black Tea", "Green Tea", "Herbal Tea", "Rooibos", "White Tea", + "Tea Cups", "Tea Pots", "Filters", "Infusers" }; + private static final String[] CATEGORYDESCRIPTIONS = { "Pure black tea and blends", "From China and Japan", + "Helps when you feel sick", "In many variations", "If green tea doesn't agree with you", + "Cups and glasses", "Classy and useful", "For extremely fine grained tea", + "No metal for green tea" }; + + private static final String[][] PRODUCTNAMES = { + { "Earl Grey (loose)", "Assam (loose)", "Darjeeling (loose)", "Frisian Black Tee (loose)", + "Anatolian Assam (loose)", "Earl Grey (20 bags)", "Assam (20 bags)", "Darjeeling (20 bags)", + "Ceylon (loose)", "Ceylon (20 bags)", "House blend (20 bags)", "Assam with Ginger (20 bags)"}, + { "Sencha (loose)", "Sencha (15 bags)", "Sencha (25 bags)", "Earl Grey Green (loose)", + "Earl Grey Green (15 bags)", "Earl Grey Green (25 bags)", "Matcha 30 g", "Matcha 50 g", + "Matcha 100 g", "Gunpowder Tea (loose)", "Gunpowder Tea (15 bags)", "Gunpowder Tea (25 bags)" }, + { "Camomile (loose)", "Camomile (15 bags)", "Peepermint (loose)", "Peppermint (15 bags)", + "Peppermint (15 bags)", "Sweet Mint (loose)", "Sweet Mint (15 bags)", "Sweet Mint (25 bags)", + "Lemongrass (loose)", "Lemongrass (20 bags)", "Chai Mate (15 bags)", "Chai Mate (25 bags)", + "Stomach Soothing Tea (15 bags)", "Headache Soothing Tea (15 bags)" }, + { "Rooibos Pure (loose)", "Rooibos Pure (20 bags)", "Rooibos Orange (loose)", "Rooibos Orange (20 bags)", + "Rooibos Coconut (loose)", "Rooibos Coconut (20 bags)", "Rooibos Vanilla (loose)", + "Rooibos Pure (20 bags)", "Rooibos Ginger (loose)", "Rooibos Pure (20 bags)", + "Rooibos Grapefruit (loose)", "Rooibos Pure (20 bags)" }, + { "White Tea (loose)", "White Tea (15 bags)", "White Tea (25 bags)", "White Chai (loose)", + "White Chai (15 bags)", "White Chai (25 bags)", "Pai Mu Tan White (loose)", + "Pai Mu Tan White (15 bags)", "Pai Mu Tan White (25 bags)", "White Apricot (loose)", + "White Apricot (15 bags)", "White Apricot (25 bags)" }, + { "Ceramic Cup White", "Ceramic Cup Blue", "Ceramic Cup Green", "Ceramic Cup Black", + "Percelain Cup White", "Porcelain Cup with Flowers", "Poercelain Cup with Dog Picture", + "Small Glass Cup", "Large Glass Cup", "Small Glass Cup with Glass Infuser", + "Large Glass Cup with Glass Infuser", "Small Glass Cup with Plastic Infuser", + "Large Glass Cup with Plastic Infuser" }, + { "Porcelain Teapot White, 2 Cups", "Porcelain Teapot White, 5 Cups", + "Porcelain Teapot with Flowers, 2 Cups", "Porcelain Teapot with Flowers, 5 Cups", + "Persian Teapot, 3 Cups", "Large Teapot with Glass Infuser, 7 Cups", + "Small Teapot with Glass Infuser, 3 Cups", "Medium Teapot with Glass Infuser, 5 Cups", + "Large Glass Teapot with Steel Infuser, 7 Cups", "Small Glass Teapot with Steel Infuser, 3 Cups", + "Medium Glass Teapot with Steel Infuser, 5 Cups", "Glass Teapot Warmer" }, + { "Filters with Drawstring, 100 pcs.", "Filters with Drawstring, 250 pcs.", + "Filters with Drawstring, 500 pcs.", "Tea Sack, 50 pcs.", "Tea Sack, 125 pcs.", + "Tea Sack, 500 pcs.", "Reusible Cotton Tea Sack, 10 pcs.", "Reusible Cotton Tea Sack, 35 pcs.", + "Reusable Cotton Tea Sack, 50 pcs.", "Pyramid-shaped Tea Filter, 10 pcs.", + "Pyramid-shaped Tea Filter, 25 pcs.", "Mr. Tea Filter, 10 pcs." }, + { "Medium Mesh Ball with Chain", "Medium Snap Mesh Ball", "Large Ball with Chain", + "Small Mesh Ball with Chain", "Small Snap Mesh Ball", "Large Snap Mesh Ball", + "Medium Silicone Ball Infuser", "Small Silicone Ball Infuser", + "Large Silicone Ball Infuser", "Small Mesh Ball with Panda Look", "Heart-shaped Infuser" } }; + + private static final String[] FIRSTNAMES = {"James", "John", "Robert", "Michael", "William", "David", + "Richard", "Charles", "Jospeph", "Thomas", "Christopher", "Daniel", "Paul", "Mark", "Donald", + "George", "Kenneth", "Steven", "Edward", "Brian", "Ronald", "Anthony", "Kevin", "Jason", + "Matthew", "Gary", "Timothy", "Jose", "Larry", "Jeffrey", "Frank", "Scott", "Eric", "Stephen", + "Andrew", "Raymond", "Gregory", "Joshua", "Jerry", "Dennis", "Walter", "Patrick", "Peter", + "Mary", "Patricia", "Barbara", "Elizabeth", "Jennifer", "Maria", "Susan", "Margaret", "Dorothy", + "Lisa", "Nancy", "Karen", "Betty", "Helen", "Sandra", "Donna", "Carol", "Ruth", "Sharon", + "Michelle", "Laura", "Sarah", "Kimberly", "Deborah", "Jessica", "Shirley", "Cynthia"}; + private static final String[] LASTNAMES = {"Smith", "Johnson", "Williams", "Jones", "Brown", "Davis", + "Miller", "Wilson", "Moorse", "Taylor", "Anderson", "Thomas", "Jackson", "White", "Harris", + "Martin", "Thompson", "Garcia", "Martinez", "Robinson", "Clark", "Rodriguez", "Lewis", "Lee", + "Walker", "Hall", "Allen", "Young", "Hernandez", "King", "Wright", "Lopez", "Hill", "Scoot"}; + + private static final int MAX_ITEMS_PER_ORDER = 10; + private static final double PREFFERED_CATEGORY_CHANCE = 0.825; + + + /** + * The data generator singleton. + */ + public static final DataGenerator GENERATOR = new DataGenerator(); + + /** + * Maintenance mode is checked on request paths (e.g., readiness/maintenance endpoints). + * Reads should be lock-free; writes are rare. + */ + private volatile boolean maintenanceMode = false; + + private DataGenerator() { + + } + + /** + * Checks if the database is empty. + * + * @return True if the database is empty. + */ + public boolean isDatabaseEmpty() { + // every other entity requires a valid category or user + return (CategoryRepository.REPOSITORY.getAllEntities(-1, 1).size() == 0 + && UserRepository.REPOSITORY.getAllEntities(-1, 1).size() == 0); + } + + /** + * Generates data for the database. Uses a fixed random seed. + * + * @param categories + * Number of categories. + * @param productsPerCategory + * Number of products per category. + * @param users + * Number of users. Password is always "password". + * @param maxOrdersPerUser + * Maximum order per user. + */ + public void generateDatabaseContent(int categories, int productsPerCategory, + int users, int maxOrdersPerUser) { + setGenerationFinishedFlag(false); + CacheManager.MANAGER.clearAllCaches(); + random = new Random(5); + generateCategories(categories); + generateProducts(productsPerCategory); + generateUsers(users); + generateOrders(maxOrdersPerUser, productsPerCategory); + setGenerationFinishedFlag(true); + CacheManager.MANAGER.clearAllCaches(); + } + + private void generateCategories(int categories) { + for (int i = 0; i < categories; i++) { + Category category = new Category(); + if (i < CATEGORYDESCRIPTIONS.length) { + category.setDescription(CATEGORYDESCRIPTIONS[i]); + } else { + int version = i / CATEGORYDESCRIPTIONS.length; + category.setDescription(CATEGORYDESCRIPTIONS[i % CATEGORYDESCRIPTIONS.length] + ", v" + version); + } + if (i < CATEGORYNAMES.length) { + category.setName(CATEGORYNAMES[i]); + } else { + int version = i / CATEGORYNAMES.length; + category.setName(CATEGORYNAMES[i % CATEGORYNAMES.length] + ", v" + version); + } + CategoryRepository.REPOSITORY.createEntity(category); + } + } + + private void generateProducts(int productsPerCategory) { + int categoryIndex = 0; + for (PersistenceCategory category : CategoryRepository.REPOSITORY.getAllEntities()) { + int productTypeIndex = categoryIndex % PRODUCTNAMES.length; + for (int i = 0; i < productsPerCategory; i++) { + int productIndex = i % PRODUCTNAMES[productTypeIndex].length; + int version = i / PRODUCTNAMES[productTypeIndex].length; + Product product = new Product(); + if (version == 0) { + product.setName(PRODUCTNAMES[productTypeIndex][productIndex]); + } else { + product.setName(PRODUCTNAMES[productTypeIndex][productIndex] + ", v" + version); + } + product.setDescription( + "Great " + category.getName() + ": " + PRODUCTNAMES[productTypeIndex][productIndex]); + product.setListPriceInCents(95 + random.nextInt(12000)); + product.setCategoryId(category.getId()); + ProductRepository.REPOSITORY.createEntity(product); + } + categoryIndex++; + } + } + + private void generateUsers(int users) { + IntStream.range(0, users).parallel().forEach(i -> { + User user = new User(); + user.setUserName("user" + i); + user.setEmail("user" + i + "@teastore.com"); + user.setRealName(FIRSTNAMES[random.nextInt(FIRSTNAMES.length)] + + " " + LASTNAMES[random.nextInt(LASTNAMES.length)]); + user.setPassword(BCrypt.hashpw(PASSWORD, BCrypt.gensalt(6))); + UserRepository.REPOSITORY.createEntity(user); + }); + } + + private void generateOrders(int maxOrdersPerUser, int productsPerCategory) { + UserRepository.REPOSITORY.getAllEntities().parallelStream().forEach(user -> { + for (int i = 0; i < random.nextInt(maxOrdersPerUser + 1); i++) { + Order order = new Order(); + order.setAddressName(user.getRealName()); + String eastWest = " East "; + if (random.nextDouble() > 0.5) { + eastWest = " West "; + } + String northSouth = " North"; + if (random.nextDouble() > 0.5) { + northSouth = " South"; + } + order.setAddress1(random.nextInt(9000) + eastWest + random.nextInt(9000) + northSouth); + order.setAddress2("District " + random.nextInt(500) + ", Utopia, " + (10000 + random.nextInt(40000))); + order.setCreditCardCompany("MasterCard"); + if (random.nextDouble() > 0.5) { + order.setCreditCardCompany("Visa"); + } + order.setCreditCardExpiryDate(LocalDate.ofYearDay(LocalDateTime.now().getYear() + + 1 + random.nextInt(10), 1 + random.nextInt(363)).format(DateTimeFormatter.ISO_LOCAL_DATE)); + order.setTime(LocalDateTime.of(LocalDateTime.now().getYear() - random.nextInt(10), + 1 + random.nextInt(10), 1 + random.nextInt(24), random.nextInt(23), random.nextInt(59)) + .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + order.setUserId(user.getId()); + order.setCreditCardNumber(fourDigits() + " " + fourDigits() + " " + fourDigits() + " " + fourDigits()); + long orderId = OrderRepository.REPOSITORY.createEntity(order); + PersistenceOrder createdOrder = OrderRepository.REPOSITORY.getEntity(orderId); + long price = 0; + List categories = CategoryRepository.REPOSITORY.getAllEntities(); + Category preferred = categories.get(random.nextInt(categories.size())); + for (int j = 0; j < 1 + random.nextInt(MAX_ITEMS_PER_ORDER); j++) { + OrderItem item = generateOrderItem(createdOrder, preferred, productsPerCategory); + price += item.getQuantity() * item.getUnitPriceInCents(); + OrderItemRepository.REPOSITORY.createEntity(item); + } + createdOrder.setTotalPriceInCents(price); + OrderRepository.REPOSITORY.updateEntity(orderId, createdOrder); + } + }); + } + + //Order and preferred category must have a valid id! + private OrderItem generateOrderItem(Order order, Category preferred, int productsPerCategory) { + OrderItem item = new OrderItem(); + item.setOrderId(order.getId()); + item.setQuantity(random.nextInt(7)); + Category itemCategory = preferred; + if (random.nextDouble() > PREFFERED_CATEGORY_CHANCE) { + List categories = CategoryRepository.REPOSITORY.getAllEntities(); + itemCategory = categories.get(random.nextInt(categories.size())); + } + Product product = ProductRepository.REPOSITORY.getAllEntities( + itemCategory.getId(), random.nextInt(productsPerCategory), 1).get(0); + item.setProductId(product.getId()); + item.setUnitPriceInCents(product.getListPriceInCents()); + return item; + } + + private String fourDigits() { + return String.valueOf(1000 + random.nextInt(8999)); + } + + /** + * Drops database and recreates all tables.
+ * Attention: Does not reset foreign persistence contexts. + * Best practice is to call CacheManager.MANAGER.resetAllEMFs() after dropping and then recreating the DB. + */ + public void dropAndCreateTables() { + CacheManager.MANAGER.clearLocalCacheOnly(); + ServerSession session = CategoryRepository.REPOSITORY.getEM().unwrap(ServerSession.class); + SchemaManager schemaManager = new SchemaManager(session); + schemaManager.replaceDefaultTables(true, true); + CacheManager.MANAGER.clearLocalCacheOnly(); + CacheManager.MANAGER.resetLocalEMF(); + setGenerationFinishedFlag(false); + CacheManager.MANAGER.clearAllCaches(); + } + + private void setGenerationFinishedFlag(boolean flag) { + EntityManager em = CategoryRepository.REPOSITORY.getEM(); + try { + em.getTransaction().begin(); + List entities = + em.createQuery("SELECT u FROM " + + DatabaseManagementEntity.class.getName() + + " u", DatabaseManagementEntity.class) + .getResultList(); + if (entities == null || entities.isEmpty()) { + DatabaseManagementEntity entity = new DatabaseManagementEntity(); + entity.setFinishedGenerating(flag); + em.persist(entity); + } else { + DatabaseManagementEntity entity = entities.get(0); + entity.setFinishedGenerating(flag); + } + em.getTransaction().commit(); + } finally { + em.close(); + } + } + + /** + * Returns true if the database has finished generating. + * False if it is currently generating. + * @return False if the database is generating. + */ + public boolean getGenerationFinishedFlag() { + if (isMaintenanceMode()) { + return false; + } + boolean finishedGenerating = false; + EntityManager em = CategoryRepository.REPOSITORY.getEM(); + try { + List entities = + em.createQuery("SELECT u FROM " + + DatabaseManagementEntity.class.getName() + + " u", DatabaseManagementEntity.class) + .getResultList(); + if (entities != null && !entities.isEmpty()) { + finishedGenerating = entities.get(0).isFinishedGenerating(); + } + } finally { + em.close(); + } + return finishedGenerating; + } + + /** + * Returns if the current persistence is in maintenance mode. + * Will return 503 on pretty much every external call in this mode. + * @return True if in maintenance, false otherwise. + */ + public boolean isMaintenanceMode() { + return maintenanceMode; + } + + /** + * Put the current persistence into maintenance mode. + * Will return 503 on pretty much every external call in this mode. + * @param maintenanceMode The maintenance flag. + */ + public void setMaintenanceModeInternal(boolean maintenanceMode) { + this.maintenanceMode = maintenanceMode; + } + + /** + * Puts all persistences into maintenance mode. + * Will return 503 on pretty much every external call once in this mode. + * @param maintenanceMode The maintenance flag. + */ + public void setMaintenanceModeGlobal(boolean maintenanceMode) { + setMaintenanceModeInternal(maintenanceMode); + List rs = ServiceLoadBalancer.multicastRESTToOtherServiceInstances( + "generatedb", String.class, client -> setMaintenanceModeExternal(client, maintenanceMode)); + rs.forEach(r -> { + if (r != null) { + r.bufferEntity(); + r.close(); + } + }); + } + + private Response setMaintenanceModeExternal(RESTClient client, final Boolean maintenanceMode) { + Response r = client.getEndpointTarget().path("maintenance") + .request(MediaType.TEXT_PLAIN).post(Entity.entity(String.valueOf(maintenanceMode), MediaType.TEXT_PLAIN)); + return r; + } +} diff --git a/services/tools.descartes.teastore.persistence/src/main/java/tools/descartes/teastore/persistence/repository/EMFManager.java b/services/tools.descartes.teastore.persistence/src/main/java/tools/descartes/teastore/persistence/repository/EMFManager.java index 3288e0aac8aca0881cb36ad8d180a33bd19b35fd..1f323586c979e2d95f1990e65c8d8f1e5042c176 100644 GIT binary patch delta 720 zcmbu5F>g~b5XWf~XzhfhqCx_dUL}USqDIOBNF_p*XeEXYNU$I=IDSr^RmXnVeyJWP z>IY!-!~#r+5$TN`nVI+qbO*ix_nedziH*UsefPh+-~GSbdE5Q`uCa6}d}uscofyTu zR3dzDY~7$Fw{pU~0Ov8v;XDiBeYTkAI<9_ZQjTGR(0BZxLiNN^OaV(z4CQq z(P97a;E-uH61J1TYf8zKR%@%Dmcl97IiUo$;b|rvjD^ok3ivV_d?p|>9&+Y0Fhdv{ zhaX{`bY5sKz>1fJaNa>j>Qq|6y(+QFnqrj2o>t_>NpGwaF6qMF#K;sFjFcHL1+LT_ z#rm0iB~nm^AHkr6!bv@X)D#0Hc6cWE(L9Oa*z{N;j$=asIcg+FYY=uK*&lzpwJw~I zyWy;{9v;xEC;QquRkdLmNvL5zpHln^2HQckO1wnmoLr$z~Mg_;c`=2tn#-_(gl|h(Ji} yAGTXh?m;X_YC1x29b76VW1!~!*Ai12s@99W|FLF9khC|e$L)_-s-J7$mVW~n8RmBY delta 294 zcmbQKcUXJFJZ7fDn)!1(US^wB2**Q76IBU5$H%oJ@U<3*s=G2{hg3E}vxH2y}qbNTw zvnn-30c6Ewer{f|%rpfJuvHMdG!?43I5{~dZ{(E#S|~C37Pl%3NXg`dqT+lgN;rW+ z5|h6OOHH=n5#|8e$H@saXL2-8ARCAcG-IKtsH~> recommenders = new HashMap<>(); - - static { - recommenders = new HashMap>(); - recommenders.put("Popularity", PopularityBasedRecommender.class); - recommenders.put("SlopeOne", SlopeOneRecommender.class); - recommenders.put("PreprocessedSlopeOne", PreprocessedSlopeOneRecommender.class); - recommenders.put("OrderBased", OrderBasedRecommender.class); - } - - /** - * The default recommender to choose, if no other recommender was set. - */ - private static final Class DEFAULT_RECOMMENDER = SlopeOneRecommender.class; - - private static final Logger LOG = LoggerFactory.getLogger(RecommenderSelector.class); - - private static RecommenderSelector instance; - - private IRecommender fallbackrecommender; - - private IRecommender recommender; - - /** - * Private Constructor. - */ - private RecommenderSelector() { - fallbackrecommender = new PopularityBasedRecommender(); - try { - String recommendername = (String) new InitialContext().lookup("java:comp/env/recommenderAlgorithm"); - // if a specific algorithm is set, we can use that algorithm - if (recommenders.containsKey(recommendername)) { - try { - recommender = recommenders.get(recommendername).getDeclaredConstructor().newInstance(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (SecurityException e) { - e.printStackTrace(); - } - } else { - LOG.warn("Recommendername: " + recommendername - + " was not found. Using default recommender (SlopeOneRecommeder)."); - try { - recommender = DEFAULT_RECOMMENDER.getDeclaredConstructor().newInstance(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (SecurityException e) { - e.printStackTrace(); - } - } - } catch (InstantiationException | IllegalAccessException e) { - // if creating a new instance fails - e.printStackTrace(); - LOG.warn("Could not create an instance of the requested recommender. Using fallback."); - recommender = fallbackrecommender; - } catch (NamingException e) { - // if nothing was set - LOG.info("Recommender not set. Using default recommender (SlopeOneRecommeder)."); - try { - try { - recommender = DEFAULT_RECOMMENDER.getDeclaredConstructor().newInstance(); - } catch (IllegalArgumentException e1) { - e1.printStackTrace(); - } catch (InvocationTargetException e1) { - e1.printStackTrace(); - } catch (NoSuchMethodException e1) { - e1.printStackTrace(); - } catch (SecurityException e1) { - e1.printStackTrace(); - } - } catch (InstantiationException | IllegalAccessException e1) { - // also the default algorithm could fail - e1.printStackTrace(); - LOG.warn("Could not create an instance of DEFAULT_RECOMMENDER " + DEFAULT_RECOMMENDER.getName() + "."); - recommender = fallbackrecommender; - } - } - } - - @Override - public List recommendProducts(Long userid, List currentItems) - throws UnsupportedOperationException { - try { - return recommender.recommendProducts(userid, currentItems); - } catch (UseFallBackException e) { - // a UseFallBackException is usually ignored (as it is conceptual and might - // occur quite often) - LOG.trace("Executing " + recommender.getClass().getName() - + " as recommender failed. Using fallback recommender. Reason:\n" + e.getMessage()); - return fallbackrecommender.recommendProducts(userid, currentItems); - } catch (UnsupportedOperationException e) { - // if algorithm is not yet trained, we throw the error - LOG.error("Executing " + recommender.getClass().getName() - + " threw an UnsupportedOperationException. The recommender was not finished with training."); - throw e; - } catch (Exception e) { - // any other exception is just reported - LOG.warn("Executing " + recommender.getClass().getName() - + " threw an unexpected error. Using fallback recommender. Reason:\n" + e.getMessage()); - return fallbackrecommender.recommendProducts(userid, currentItems); - } - } - - /** - * Returns the instance of this Singleton or creates a new one, if this is the - * first call of this method. - * - * @return The instance of this class. - */ - public static synchronized RecommenderSelector getInstance() { - if (instance == null) { - instance = new RecommenderSelector(); - } - return instance; - } - - /* - * (non-Javadoc) - * - * @see - * tools.descartes.teastore.recommender.IRecommender#train(java.util.List, - * java.util.List) - */ - @Override - public void train(List orderItems, List orders) { - recommender.train(orderItems, orders); - fallbackrecommender.train(orderItems, orders); - } - -} +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tools.descartes.teastore.recommender.algorithm; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import tools.descartes.teastore.recommender.algorithm.impl.UseFallBackException; +import tools.descartes.teastore.recommender.algorithm.impl.cf.PreprocessedSlopeOneRecommender; +import tools.descartes.teastore.recommender.algorithm.impl.cf.SlopeOneRecommender; +import tools.descartes.teastore.recommender.algorithm.impl.orderbased.OrderBasedRecommender; +import tools.descartes.teastore.recommender.algorithm.impl.pop.PopularityBasedRecommender; +import tools.descartes.teastore.entities.Order; +import tools.descartes.teastore.entities.OrderItem; + +/** + * A strategy selector for the Recommender functionality. + * + * @author Johannes Grohmann + * + */ +public final class RecommenderSelector implements IRecommender { + + /** + * This map lists all currently available recommending approaches and assigns + * them their "name" for the environment variable. + */ + private static Map> recommenders = new HashMap<>(); + + static { + recommenders = new HashMap>(); + recommenders.put("Popularity", PopularityBasedRecommender.class); + recommenders.put("SlopeOne", SlopeOneRecommender.class); + recommenders.put("PreprocessedSlopeOne", PreprocessedSlopeOneRecommender.class); + recommenders.put("OrderBased", OrderBasedRecommender.class); + } + + /** + * The default recommender to choose, if no other recommender was set. + */ + private static final Class DEFAULT_RECOMMENDER = SlopeOneRecommender.class; + + private static final Logger LOG = LoggerFactory.getLogger(RecommenderSelector.class); + + private static final RecommenderSelector INSTANCE = new RecommenderSelector(); + + private final IRecommender fallbackrecommender; + + private final IRecommender recommender; + + /** + * Private Constructor. + */ + private RecommenderSelector() { + fallbackrecommender = new PopularityBasedRecommender(); + IRecommender selected; + try { + String recommendername = (String) new InitialContext().lookup("java:comp/env/recommenderAlgorithm"); + // if a specific algorithm is set, we can use that algorithm + if (recommenders.containsKey(recommendername)) { + selected = instantiate(recommenders.get(recommendername)); + } else { + LOG.warn("Recommendername: " + recommendername + + " was not found. Using default recommender (SlopeOneRecommeder)."); + selected = instantiate(DEFAULT_RECOMMENDER); + } + } catch (NamingException e) { + // if nothing was set + LOG.info("Recommender not set. Using default recommender (SlopeOneRecommeder)."); + selected = instantiate(DEFAULT_RECOMMENDER); + } + recommender = selected != null ? selected : fallbackrecommender; + } + + private static IRecommender instantiate(Class clazz) { + try { + return clazz.getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + LOG.warn("Could not create an instance of the requested recommender. Using fallback."); + return null; + } catch (IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public List recommendProducts(Long userid, List currentItems) throws UnsupportedOperationException { + try { + return recommender.recommendProducts(userid, currentItems); + } catch (UseFallBackException e) { + // a UseFallBackException is usually ignored (as it is conceptual and might + // occur quite often) + LOG.trace("Executing " + recommender.getClass().getName() + + " as recommender failed. Using fallback recommender. Reason:\n" + e.getMessage()); + return fallbackrecommender.recommendProducts(userid, currentItems); + } catch (UnsupportedOperationException e) { + // if algorithm is not yet trained, we throw the error + LOG.error("Executing " + recommender.getClass().getName() + + " threw an UnsupportedOperationException. The recommender was not finished with training."); + throw e; + } catch (Exception e) { + // any other exception is just reported + LOG.warn("Executing " + recommender.getClass().getName() + + " threw an unexpected error. Using fallback recommender. Reason:\n" + e.getMessage()); + return fallbackrecommender.recommendProducts(userid, currentItems); + } + } + + /** + * Returns the instance of this Singleton. + * + * @return The instance of this class. + */ + public static RecommenderSelector getInstance() { + return INSTANCE; + } + + @Override + public void train(List orderItems, List orders) { + recommender.train(orderItems, orders); + fallbackrecommender.train(orderItems, orders); + } + +} diff --git a/services/tools.descartes.teastore.recommender/src/main/java/tools/descartes/teastore/recommender/servlet/TrainingSynchronizer.java b/services/tools.descartes.teastore.recommender/src/main/java/tools/descartes/teastore/recommender/servlet/TrainingSynchronizer.java index 58cb7feb1..5c8a98ec8 100644 --- a/services/tools.descartes.teastore.recommender/src/main/java/tools/descartes/teastore/recommender/servlet/TrainingSynchronizer.java +++ b/services/tools.descartes.teastore.recommender/src/main/java/tools/descartes/teastore/recommender/servlet/TrainingSynchronizer.java @@ -60,7 +60,7 @@ public final class TrainingSynchronizer { private static final List PERSISTENCE_CREATION_WAIT_TIME = Arrays.asList(1000, 2000, 5000, 10000, 30000, 60000); - private static TrainingSynchronizer instance; + private static final TrainingSynchronizer INSTANCE = new TrainingSynchronizer(); private boolean isReady = false; @@ -88,11 +88,8 @@ private TrainingSynchronizer() { * * @return An instance of {@link TrainingSynchronizer} */ - public static synchronized TrainingSynchronizer getInstance() { - if (instance == null) { - instance = new TrainingSynchronizer(); - } - return instance; + public static TrainingSynchronizer getInstance() { + return INSTANCE; } private static final Logger LOG = LoggerFactory.getLogger(TrainingSynchronizer.class); @@ -139,8 +136,8 @@ private void waitForPersistence() { client -> client.getService().path(client.getApplicationURI()).path(client.getEndpointURI()) .path("finished").request().get()); - if (result != null && Boolean.parseBoolean(result.readEntity(String.class))) { - break; + if (result != null && Boolean.parseBoolean(result.readEntity(String.class))) { + break; } } catch (NullPointerException | NotFoundException | LoadBalancerTimeoutException e) { // continue waiting as usual diff --git a/services/tools.descartes.teastore.webui/src/main/java/tools/descartes/teastore/webui/servlet/AbstractUIServlet.java b/services/tools.descartes.teastore.webui/src/main/java/tools/descartes/teastore/webui/servlet/AbstractUIServlet.java index d39af623d..59f90d15a 100644 --- a/services/tools.descartes.teastore.webui/src/main/java/tools/descartes/teastore/webui/servlet/AbstractUIServlet.java +++ b/services/tools.descartes.teastore.webui/src/main/java/tools/descartes/teastore/webui/servlet/AbstractUIServlet.java @@ -90,6 +90,12 @@ public abstract class AbstractUIServlet extends HttpServlet { */ protected static final String REMOVEPRODUCT = "Product %s is removed from cart!"; + /** + * ObjectMapper is thread-safe after configuration and relatively expensive to create. + * WebUI does cookie (de)serialization on every request; reuse a single instance. + */ + private static final ObjectMapper JSON = new ObjectMapper(); + /** * Try to read the SessionBlob from the cookie. If no SessioBlob exist, a new * SessionBlob is created. If the SessionBlob is corrupted, an @@ -102,12 +108,12 @@ protected SessionBlob getSessionBlob(HttpServletRequest request) { if (request.getCookies() != null) { for (Cookie cook : request.getCookies()) { if (cook.getName().equals(BLOB)) { - ObjectMapper o = new ObjectMapper(); try { - SessionBlob blob = o.readValue(URLDecoder.decode(cook.getValue(), "UTF-8"), SessionBlob.class); + SessionBlob blob = JSON.readValue(URLDecoder.decode(cook.getValue(), "UTF-8"), + SessionBlob.class); if (blob != null) { - return blob; - } + return blob; + } } catch (IOException e) { throw new IllegalStateException("Cookie corrupted!"); } @@ -125,9 +131,8 @@ protected SessionBlob getSessionBlob(HttpServletRequest request) { * @param response servlet response */ protected void saveSessionBlob(SessionBlob blob, HttpServletResponse response) { - ObjectMapper o = new ObjectMapper(); try { - Cookie cookie = new Cookie(BLOB, URLEncoder.encode(o.writeValueAsString(blob), "UTF-8")); + Cookie cookie = new Cookie(BLOB, URLEncoder.encode(JSON.writeValueAsString(blob), "UTF-8")); response.addCookie(cookie); } catch (JsonProcessingException | UnsupportedEncodingException e) { throw new IllegalStateException("Could not save blob!"); @@ -142,9 +147,8 @@ protected void saveSessionBlob(SessionBlob blob, HttpServletResponse response) { * @param response servlet response */ protected void destroySessionBlob(SessionBlob blob, HttpServletResponse response) { - ObjectMapper o = new ObjectMapper(); try { - Cookie cookie = new Cookie(BLOB, URLEncoder.encode(o.writeValueAsString(blob), "UTF-8")); + Cookie cookie = new Cookie(BLOB, URLEncoder.encode(JSON.writeValueAsString(blob), "UTF-8")); cookie.setMaxAge(0); response.addCookie(cookie); } catch (JsonProcessingException | UnsupportedEncodingException e) { diff --git a/utilities/tools.descartes.teastore.registryclient/src/main/java/tools/descartes/teastore/registryclient/rest/TrackingFilter.java b/utilities/tools.descartes.teastore.registryclient/src/main/java/tools/descartes/teastore/registryclient/rest/TrackingFilter.java index 994f488e7..c730d20b8 100644 --- a/utilities/tools.descartes.teastore.registryclient/src/main/java/tools/descartes/teastore/registryclient/rest/TrackingFilter.java +++ b/utilities/tools.descartes.teastore.registryclient/src/main/java/tools/descartes/teastore/registryclient/rest/TrackingFilter.java @@ -72,15 +72,16 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } HttpServletRequest req = (HttpServletRequest) request; String sessionId = SESSION_REGISTRY.recallThreadLocalSessionId(); - long traceId = -1L; + long traceId; int eoi; int ess; final String operationExecutionHeader = req.getHeader(HEADER_FIELD); - if ((operationExecutionHeader == null) || (operationExecutionHeader.equals(""))) { - LOG.debug("No monitoring data found in the incoming request header"); - // LOG.info("Will continue without sending back reponse header"); + if (operationExecutionHeader == null || operationExecutionHeader.isEmpty()) { + if (LOG.isDebugEnabled()) { + LOG.debug("No monitoring data found in the incoming request header"); + } traceId = CF_REGISTRY.getAndStoreUniqueThreadLocalTraceId(); CF_REGISTRY.storeThreadLocalEOI(0); CF_REGISTRY.storeThreadLocalESS(1); // next operation is ess + 1 @@ -88,7 +89,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha ess = 0; } else { if (LOG.isDebugEnabled()) { - LOG.debug("Received request: " + req.getMethod() + "with header = " + operationExecutionHeader); + // Avoid string concatenation when debug is disabled. + LOG.debug("Received request: {} with header = {}", req.getMethod(), operationExecutionHeader); } final String[] headerArray = operationExecutionHeader.split(","); @@ -122,6 +124,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha try { traceId = Long.parseLong(traceIdStr); } catch (final NumberFormatException exc) { + traceId = CF_REGISTRY.getUniqueTraceId(); LOG.warn("Invalid trace id", exc); } } else { @@ -150,7 +153,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha long traceId = CF_REGISTRY.recallThreadLocalTraceId(); int eoi = CF_REGISTRY.recallThreadLocalEOI(); wrappedResponse.addHeader(HEADER_FIELD, - traceId + "," + sessionId + "," + (eoi) + "," + Integer.toString(CF_REGISTRY.recallThreadLocalESS())); + traceId + "," + sessionId + "," + eoi + "," + Integer.toString(CF_REGISTRY.recallThreadLocalESS())); out.write(wrappedResponse.toString()); } } diff --git a/utilities/tools.descartes.teastore.registryclient/src/main/java/tools/descartes/teastore/registryclient/util/RESTClient.java b/utilities/tools.descartes.teastore.registryclient/src/main/java/tools/descartes/teastore/registryclient/util/RESTClient.java index 874d7e3ea..5b7f642f0 100644 --- a/utilities/tools.descartes.teastore.registryclient/src/main/java/tools/descartes/teastore/registryclient/util/RESTClient.java +++ b/utilities/tools.descartes.teastore.registryclient/src/main/java/tools/descartes/teastore/registryclient/util/RESTClient.java @@ -17,11 +17,11 @@ import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.HostnameVerifier; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.WebTarget; @@ -40,10 +40,6 @@ */ public class RESTClient { - /** - * Default and max size for connection pools. We estimate a good size by using the available processor count. - */ - private static final int DEFAULT_CONNECT_TIMEOUT = 600; private static final int DEFAULT_READ_TIMEOUT = 6000; @@ -55,15 +51,27 @@ public class RESTClient { private static int readTimeout = DEFAULT_READ_TIMEOUT; private static int connectTimeout = DEFAULT_CONNECT_TIMEOUT; + /** + * Reused, thread-safe jersey client. Creating a Client is expensive (providers, connectors, pools), + * so we keep a singleton and rely on connector-level keep-alive/connection reuse. + */ + private static volatile Client SHARED_CLIENT_HTTP; + private static volatile Client SHARED_CLIENT_HTTPS; + + /** + * For HTTPS we were historically disabling TLS verification globally through HttpsURLConnection + * defaults. We keep the behavior but ensure it is applied only once. + */ + private static volatile boolean HTTPS_DEFAULTS_INSTALLED; + private String applicationURI; private String endpointURI; - private Client client; - private WebTarget service; - private Class entityClass; + private final Client client; + private final WebTarget service; + private final Class entityClass; - private ParameterizedType parameterizedGenericType; - private GenericType> genericListType; + private final GenericType> genericListType; /** * Creates a new REST Client for an entity of Type T. The client interacts with a Server providing @@ -76,24 +84,111 @@ public class RESTClient { * open for interpretation by the inheriting REST clients. */ public RESTClient(String hostURL, String application, String endpoint, final Class entityClass) { - boolean useHTTPS = "true".equals(System.getenv("USE_HTTPS")); + final boolean useHTTPS = "true".equals(System.getenv("USE_HTTPS")); if (!hostURL.endsWith("/")) { hostURL += "/"; } if (!hostURL.contains("://")) { - if (useHTTPS) { - hostURL = "https://" + hostURL; - } else { - hostURL = "http://" + hostURL; + hostURL = (useHTTPS ? "https://" : "http://") + hostURL; + } + + client = getOrCreateSharedClient(useHTTPS); + service = client.target(UriBuilder.fromUri(hostURL).build()); + applicationURI = application; + endpointURI = endpoint; + this.entityClass = entityClass; + + final ParameterizedType parameterizedGenericType = new ParameterizedType() { + public Type[] getActualTypeArguments() { + return new Type[] { entityClass }; + } + + public Type getRawType() { + return List.class; + } + + public Type getOwnerType() { + return List.class; + } + }; + genericListType = new GenericType>(parameterizedGenericType) { }; + } + + private static Client getOrCreateSharedClient(boolean useHTTPS) { + if (useHTTPS) { + Client local = SHARED_CLIENT_HTTPS; + if (local != null) { + return local; + } + synchronized (RESTClient.class) { + local = SHARED_CLIENT_HTTPS; + if (local == null) { + SHARED_CLIENT_HTTPS = local = buildClient(true); + } + return local; + } + } else { + Client local = SHARED_CLIENT_HTTP; + if (local != null) { + return local; + } + synchronized (RESTClient.class) { + local = SHARED_CLIENT_HTTP; + if (local == null) { + SHARED_CLIENT_HTTP = local = buildClient(false); + } + return local; } } + } + + private static Client buildClient(boolean useHTTPS) { ClientConfig config = new ClientConfig(); config.property(ClientProperties.CONNECT_TIMEOUT, connectTimeout); config.property(ClientProperties.READ_TIMEOUT, readTimeout); config.connectorProvider(new GrizzlyConnectorProvider()); - if (useHTTPS) { + if (!useHTTPS) { + return ClientBuilder.newClient(config); + } + + installHttpsDefaultsOnce(); + try { + TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) { + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) { + } + + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + }}; + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + + ClientBuilder builder = ClientBuilder.newBuilder().withConfig(config); + builder.sslContext(sslContext); + return builder.build(); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + // Preserve previous behavior (stack trace), but avoid failing construction entirely. + e.printStackTrace(); + return ClientBuilder.newClient(config); + } + } + + private static void installHttpsDefaultsOnce() { + if (HTTPS_DEFAULTS_INSTALLED) { + return; + } + synchronized (RESTClient.class) { + if (HTTPS_DEFAULTS_INSTALLED) { + return; + } try { TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { @Override @@ -107,43 +202,17 @@ public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certific public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } - } - }; + }}; SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); HostnameVerifier allHostsValid = (hostname, session) -> true; HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); - - ClientBuilder builder = ClientBuilder.newBuilder().withConfig(config); - builder.sslContext(sslContext); - client = builder.build(); } catch (NoSuchAlgorithmException | KeyManagementException e) { e.printStackTrace(); } - } else { - client = ClientBuilder.newClient(config); + HTTPS_DEFAULTS_INSTALLED = true; } - - service = client.target(UriBuilder.fromUri(hostURL).build()); - applicationURI = application; - endpointURI = endpoint; - this.entityClass = entityClass; - - parameterizedGenericType = new ParameterizedType() { - public Type[] getActualTypeArguments() { - return new Type[] { entityClass }; - } - - public Type getRawType() { - return List.class; - } - - public Type getOwnerType() { - return List.class; - } - }; - genericListType = new GenericType>(parameterizedGenericType) { }; } /** @@ -152,6 +221,8 @@ public Type getOwnerType() { */ public static void setGlobalReadTimeout(int readTimeout) { RESTClient.readTimeout = readTimeout; + // Ensure future clients use new values. Existing shared clients keep their config. + // (This matches current behavior: existing instances do not retroactively change timeouts.) } /** From aedfa1b5c6e7d30f91e6201f568fae12ed1d312e Mon Sep 17 00:00:00 2001 From: Parth Patil Date: Fri, 23 Jan 2026 15:42:51 -0500 Subject: [PATCH 2/2] Fixed docker build things --- examples/docker/docker-compose_default.yaml | 14 +++++++------- tools/build_docker.sh | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/docker/docker-compose_default.yaml b/examples/docker/docker-compose_default.yaml index c1f5aa7f2..8a5d8de7c 100644 --- a/examples/docker/docker-compose_default.yaml +++ b/examples/docker/docker-compose_default.yaml @@ -1,17 +1,17 @@ version: '3' services: registry: - image: descartesresearch/teastore-registry + image: gpt-teastore/teastore-registry expose: - "8080" db: - image: descartesresearch/teastore-db + image: gpt-teastore/teastore-db expose: - "3306" ports: - "3306:3306" persistence: - image: descartesresearch/teastore-persistence + image: gpt-teastore/teastore-persistence expose: - "8080" environment: @@ -20,28 +20,28 @@ services: DB_HOST: "db" DB_PORT: "3306" auth: - image: descartesresearch/teastore-auth + image: gpt-teastore/teastore-auth expose: - "8080" environment: HOST_NAME: "auth" REGISTRY_HOST: "registry" image: - image: descartesresearch/teastore-image + image: gpt-teastore/teastore-image expose: - "8080" environment: HOST_NAME: "image" REGISTRY_HOST: "registry" recommender: - image: descartesresearch/teastore-recommender + image: gpt-teastore/teastore-recommender expose: - "8080" environment: HOST_NAME: "recommender" REGISTRY_HOST: "registry" webui: - image: descartesresearch/teastore-webui + image: gpt-teastore/teastore-webui expose: - "8080" environment: diff --git a/tools/build_docker.sh b/tools/build_docker.sh index 8636072f3..2966bc60e 100755 --- a/tools/build_docker.sh +++ b/tools/build_docker.sh @@ -32,7 +32,7 @@ then perl -i -pe's|.*FROM '"${registry}"'|FROM descartesresearch/|g' ../services/tools.descartes.teastore.*/Dockerfile docker buildx rm mybuilder else - registry='descartesresearch/' + registry='gpt-teastore/' docker buildx build -t "${registry}teastore-db" ../utilities/tools.descartes.teastore.database/ --load docker buildx build -t "${registry}teastore-kieker-rabbitmq" ../utilities/tools.descartes.teastore.kieker.rabbitmq/ --load docker buildx build -t "${registry}teastore-base" ../utilities/tools.descartes.teastore.dockerbase/ --load