diff --git a/pom.xml b/pom.xml index e44885a..7e0cf9b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.vaadin.tatu vaadincreate-root pom - 1.0-SNAPSHOT + 2.0-SNAPSHOT VaadinCreate 23 Root Project diff --git a/vaadincreate-backend/pom.xml b/vaadincreate-backend/pom.xml index 40c26f0..095c5c3 100644 --- a/vaadincreate-backend/pom.xml +++ b/vaadincreate-backend/pom.xml @@ -4,7 +4,7 @@ vaadincreate-root org.vaadin.tatu - 1.0-SNAPSHOT + 2.0-SNAPSHOT 4.0.0 @@ -35,6 +35,17 @@ logback-classic ${logback.version} + + org.hibernate + hibernate-core + 5.6.0.Final + + + com.h2database + h2 + 2.3.232 + runtime + javax.persistence javax.persistence-api diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/AppDataService.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/AppDataService.java index 1e6cbe3..5201529 100644 --- a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/AppDataService.java +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/AppDataService.java @@ -1,7 +1,7 @@ package org.vaadin.tatu.vaadincreate.backend; import org.vaadin.tatu.vaadincreate.backend.data.Message; -import org.vaadin.tatu.vaadincreate.backend.mock.MockAppDataService; +import org.vaadin.tatu.vaadincreate.backend.service.AppDataServiceImpl; public interface AppDataService { @@ -10,7 +10,7 @@ public interface AppDataService { public abstract Message getMessage(); public static AppDataService get() { - return MockAppDataService.getInstance(); + return AppDataServiceImpl.getInstance(); } } diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/ProductDataService.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/ProductDataService.java index 8a4c6a7..e19f933 100644 --- a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/ProductDataService.java +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/ProductDataService.java @@ -5,12 +5,12 @@ import org.vaadin.tatu.vaadincreate.backend.data.Category; import org.vaadin.tatu.vaadincreate.backend.data.Product; -import org.vaadin.tatu.vaadincreate.backend.mock.MockProductDataService; +import org.vaadin.tatu.vaadincreate.backend.service.ProductDataServiceImpl; /** * Back-end service interface for retrieving and updating product data. */ -public abstract class ProductDataService { +public interface ProductDataService { /** * Get all Products in the database. @@ -46,7 +46,7 @@ public abstract class ProductDataService { * @throws IllegalArgumentException * if product did not exists */ - public abstract void deleteProduct(int productId); + public abstract void deleteProduct(Integer productId); /** * Find a Product from database using id @@ -55,7 +55,7 @@ public abstract class ProductDataService { * id of the Product * @return Product if it was found, otherwise null */ - public abstract Product getProductById(int productId); + public abstract Product getProductById(Integer productId); /** * Saves a new category or updates an existing one. @@ -72,7 +72,7 @@ public abstract class ProductDataService { * @param categoryId * the ID of the category to delete */ - public abstract void deleteCategory(int categoryId); + public abstract void deleteCategory(Integer categoryId); /** * Find categories by their ids. @@ -102,22 +102,7 @@ public abstract class ProductDataService { */ public abstract Product findDraft(String userName); - /** - * Only to be used in the tests. - * - * @return Products - */ - public abstract Collection backup(); - - /** - * Only to be used in the tests. - * - * @param data - * Products - */ - public abstract void restore(Collection data); - public static ProductDataService get() { - return MockProductDataService.getInstance(); + return ProductDataServiceImpl.getInstance(); } } diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/UserService.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/UserService.java index 4bfcd01..35b36db 100644 --- a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/UserService.java +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/UserService.java @@ -4,7 +4,7 @@ import java.util.Optional; import org.vaadin.tatu.vaadincreate.backend.data.User; -import org.vaadin.tatu.vaadincreate.backend.mock.MockUserService; +import org.vaadin.tatu.vaadincreate.backend.service.UserServiceImpl; public interface UserService { @@ -14,12 +14,12 @@ public interface UserService { public abstract List getAllUsers(); - public static UserService get() { - return MockUserService.getInstance(); - } + public User getUserById(Integer userId); - public User getUserById(int userId); + void removeUser(Integer userId); - void removeUser(int userId); + public static UserService get() { + return UserServiceImpl.getInstance(); + } } diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/dao/HibernateUtil.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/dao/HibernateUtil.java new file mode 100644 index 0000000..9e2cd31 --- /dev/null +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/dao/HibernateUtil.java @@ -0,0 +1,137 @@ +package org.vaadin.tatu.vaadincreate.backend.dao; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; + +/** + * Utility class for managing Hibernate sessions and transactions. + */ +public class HibernateUtil { + + // Private constructor to prevent instantiation + private HibernateUtil() { + throw new UnsupportedOperationException("Utility class"); + } + + private static SessionFactory sessionFactory; + + static { + try { + sessionFactory = new Configuration().configure() + .buildSessionFactory(); + } catch (Exception ex) { + throw new ExceptionInInitializerError(ex); + } + } + + /** + * Retrieves the singleton instance of the SessionFactory. + * + * @return the SessionFactory instance used for creating Hibernate sessions. + */ + public static SessionFactory getSessionFactory() { + return sessionFactory; + } + + /** + * Executes a function within a database transaction. + * + * @param + * The type of the result returned by the transaction function. + * @param transaction + * A function that takes a Hibernate {@link Session} and returns + * a result. + * @return The result of the transaction function. + * @throws Exception + * if the transaction fails and is rolled back. + */ + public static T inTransaction(Function transaction) { + var session = getSessionFactory().openSession(); + var tx = session.beginTransaction(); + T result; + try { + result = transaction.apply(session); + tx.commit(); + } catch (Exception e) { + tx.rollback(); + throw e; + } finally { + session.close(); + } + return result; + } + + /** + * Executes a given transaction within a Hibernate session. + *

+ * This method opens a new session, begins a transaction, and executes the + * provided {@link Consumer} with the session. If the transaction is + * successful, it commits the transaction. If an exception occurs, it rolls + * back the transaction and rethrows the exception. The session is closed in + * the finally block to ensure it is always closed. + * + * @param transaction + * the {@link Consumer} that contains the operations to be + * performed within the transaction + * @throws Exception + * if an error occurs during the transaction, it is propagated + * after rolling back the transaction + */ + public static void inTransaction(Consumer transaction) { + var session = getSessionFactory().openSession(); + var tx = session.beginTransaction(); + try { + transaction.accept(session); + tx.commit(); + } catch (Exception e) { + tx.rollback(); + throw e; + } finally { + session.close(); + } + } + + /** + * Executes a task within a Hibernate session and ensures the session is + * closed after the task is completed. + * + * @param + * The type of the result returned by the task. + * @param task + * A function that takes a Hibernate {@link Session} and returns + * a result of type T. + * @return The result of the task. + */ + public static T inSession(Function task) { + T result; + var session = getSessionFactory().openSession(); + try { + result = task.apply(session); + } finally { + session.close(); + } + return result; + } + + /** + * Executes a task within a Hibernate session. The session is opened before + * the task is executed and closed after the task completes, ensuring proper + * resource management. + * + * @param task + * a {@link Consumer} that accepts a {@link Session} and performs + * operations within that session. + */ + public static void inSession(Consumer task) { + var session = getSessionFactory().openSession(); + try { + task.accept(session); + } finally { + session.close(); + } + } +} diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/dao/ProductDao.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/dao/ProductDao.java new file mode 100644 index 0000000..b664970 --- /dev/null +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/dao/ProductDao.java @@ -0,0 +1,213 @@ +package org.vaadin.tatu.vaadincreate.backend.dao; + +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.vaadin.tatu.vaadincreate.backend.data.Category; +import org.vaadin.tatu.vaadincreate.backend.data.Product; + +/** + * Data access object for managing products and categories. + */ +@SuppressWarnings("java:S1602") +public class ProductDao { + + /** + * Updates an existing product or saves a new product if it does not have an + * ID. + * + * This method logs the product being persisted and uses a transaction to + * either update the existing product or save a new product. It then + * retrieves and returns the persisted product. + * + * @param product + * the product to be updated or saved + * @return the persisted product with the updated information + */ + public Product updateProduct(Product product) { + logger.info("Persisting Product: ({}) '{}'", product.getId(), + product.getProductName()); + var identifier = HibernateUtil.inTransaction(session -> { + Integer id; + if (product.getId() != null) { + session.update(product); + id = product.getId(); + } else { + id = (Integer) session.save(product); + } + return id; + }); + return HibernateUtil.inSession(session -> { + return session.get(Product.class, identifier); + }); + } + + /** + * Retrieves a Product by its unique identifier. + * + * @param id + * the unique identifier of the Product to be fetched + * @return the Product object corresponding to the given id, or null if no + * such Product exists + */ + public Product getProduct(Integer id) { + logger.info("Fetching Product: ({})", id); + return HibernateUtil.inSession(session -> { + return session.get(Product.class, id); + }); + } + + /** + * Deletes a product from the database based on the provided product ID. + * + * @param id + * the ID of the product to be deleted + */ + public void deleteProduct(Integer id) { + logger.info("Deleting Product: ({})", id); + HibernateUtil.inTransaction(session -> { + Product product = session.get(Product.class, id); + session.delete(product); + }); + } + + /** + * Retrieves a collection of products that belong to the specified category. + * + * @param category + * the category for which products are to be fetched + * @return a collection of products that belong to the specified category + */ + public Collection getProductsByCategory(Category category) { + logger.info("Fetching Products by Category: ({}) '{}'", + category.getId(), category.getName()); + return HibernateUtil.inSession(session -> { + return session.createQuery( + "select p from Product p join p.category c where c.id = :id", + Product.class).setParameter("id", category.getId()).list(); + }); + } + + /** + * Retrieves all products from the database. + * + * This method uses HibernateUtil to open a session and execute a query that + * fetches all instances of the Product class from the database. + * + * @return a collection of all products available in the database. + */ + public Collection getAllProducts() { + // Method returns all products from the database using HibernateUtil + logger.info("Fetching all Products"); + return HibernateUtil.inSession(session -> { + return session.createQuery("from Product", Product.class).list(); + }); + } + + /** + * Updates the given Category in the database. If the Category has an ID, it + * will be updated. Otherwise, a new Category will be created and its ID + * will be assigned. + * + * @param category + * the Category to be updated or created + * @return the updated or newly created Category + */ + public Category updateCategory(Category category) { + logger.info("Persisting Category: ({}) '{}'", category.getId(), + category.getName()); + var identifier = HibernateUtil.inTransaction(session -> { + Integer id; + if (category.getId() != null) { + session.update(category); + id = category.getId(); + } else { + id = (Integer) session.save(category); + } + return id; + }); + return HibernateUtil.inSession(session -> { + return session.get(Category.class, identifier); + }); + } + + /** + * Retrieves a Category object from the database based on the provided ID. + * + * @param id + * the ID of the Category to be fetched + * @return the Category object corresponding to the provided ID, or null if + * not found + */ + public Category getCategory(Integer id) { + logger.info("Fetching Category: ({})", id); + return HibernateUtil.inSession(session -> { + return session.get(Category.class, id); + }); + } + + /** + * Retrieves all categories from the database. + * + * @return a collection of all categories. + */ + public Collection getAllCategories() { + logger.info("Fetching all Categories"); + return HibernateUtil.inSession(session -> { + return session.createQuery("from Category", Category.class).list(); + }); + } + + /** + * Fetches a set of Category objects based on their IDs. + * + * @param ids + * a set of integer IDs representing the categories to be fetched + * @return a set of Category objects corresponding to the provided IDs + */ + public Set getCategoriesByIds(Set ids) { + logger.info("Fetching Categories: {}", ids); + return HibernateUtil.inSession(session -> { + return session + .createQuery("from Category where id in (:ids)", + Category.class) + .setParameter("ids", ids).list().stream() + .collect(Collectors.toSet()); + }); + } + + /** + * Deletes a category by its ID. This method performs the following steps: + * 1. Retrieves the category from the database using the provided ID. 2. + * Finds all products associated with the category. 3. For each product, + * removes the category from its list of categories and updates the product + * in the database. 4. Deletes the category from the database. All + * operations are performed within a single transaction. + * + * @param id + * the ID of the category to be deleted + */ + public void deleteCategory(Integer id) { + HibernateUtil.inTransaction(session -> { + var category = session.get(Category.class, id); + var list = session.createQuery( + "select p from Product p join p.category c where c.id = :id", + Product.class).setParameter("id", category.getId()).list(); + list.forEach(p -> { + if (p.getCategory().contains(category)) { + var cats = p.getCategory(); + cats.remove(category); + p.setCategory(cats); + session.update(p); + } + }); + session.delete(category); + }); + } + + private Logger logger = LoggerFactory.getLogger(this.getClass()); + +} \ No newline at end of file diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/dao/UserDao.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/dao/UserDao.java new file mode 100644 index 0000000..a6009c4 --- /dev/null +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/dao/UserDao.java @@ -0,0 +1,99 @@ +package org.vaadin.tatu.vaadincreate.backend.dao; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.vaadin.tatu.vaadincreate.backend.data.User; + +/** + * Data access object class for managing User entities. + */ +@SuppressWarnings("java:S1602") +public class UserDao { + + /** + * Finds a user by their name. + * + * @param name + * the name of the user to find + * @return the user with the specified name, or null if no such user exists + */ + public User findByName(String name) { + logger.info("Finding user {}", name); + return HibernateUtil.inSession(session -> { + return session + .createQuery("from User where name = :name", User.class) + .setParameter("name", name).uniqueResult(); + }); + } + + /** + * Updates the given user in the database. If the user already has an ID, it + * updates the existing record. Otherwise, it saves the new user and assigns + * an ID to it. + * + * @param user + * the User object to be updated or saved + * @return the updated User object retrieved from the database + */ + public User updateUser(User user) { + logger.info("Persisting User: ({}) '{}'", user.getId(), user.getName()); + var identifier = HibernateUtil.inTransaction(session -> { + Integer id; + if (user.getId() != null) { + session.update(user); + id = user.getId(); + } else { + id = (Integer) session.save(user); + } + return id; + }); + return HibernateUtil.inSession(session -> { + return session.get(User.class, identifier); + }); + } + + /** + * Retrieves a User entity from the database based on the provided user ID. + * + * @param userId + * the ID of the user to be fetched + * @return the User entity corresponding to the given user ID, or null if no + * such user exists + */ + public User getUserById(Integer userId) { + logger.info("Fetching User: ({})", userId); + return HibernateUtil.inSession(session -> { + return session.get(User.class, userId); + }); + } + + /** + * Removes a user from the database based on the provided user ID. + * + * @param userId + * the ID of the user to be removed + */ + public void removeUser(Integer userId) { + logger.info("Deleting User: ({})", userId); + HibernateUtil.inTransaction(session -> { + User user = session.get(User.class, userId); + session.delete(user); + }); + } + + /** + * Retrieves a list of all users from the database. + * + * @return a List of User objects representing all users in the database. + */ + public List getAllUsers() { + logger.info("Fetching all Users"); + return HibernateUtil.inSession(session -> { + return session.createQuery("from User", User.class).list(); + }); + } + + private Logger logger = LoggerFactory.getLogger(this.getClass()); +} diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/AbstractEntity.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/AbstractEntity.java index ff5d636..34f4e21 100644 --- a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/AbstractEntity.java +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/AbstractEntity.java @@ -3,42 +3,48 @@ import java.io.Serializable; import java.util.Objects; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; -import javax.persistence.OptimisticLockException; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import javax.persistence.SequenceGenerator; +import javax.persistence.Version; +@MappedSuperclass @SuppressWarnings("serial") public abstract class AbstractEntity implements Serializable { - @NotNull - @Min(0) - int id = -1; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idgenerator") + @SequenceGenerator(name = "idgenerator", initialValue = 1) + Integer id; - int version = 0; + @Version + Integer version; - public int getId() { + public Integer getId() { return id; } - public void setId(int id) { + public void setId(Integer id) { this.id = id; } - public int getVersion() { + public Integer getVersion() { return version; } - public void setVersion(int version) { - if (version - this.version == 1) { - this.version = version; - } else { - throw new OptimisticLockException(this); - } + public void setVersion(Integer version) { + this.version = version; } @Override public int hashCode() { - return Objects.hash(id); + if (id != null) { + return Objects.hash(id); + } else { + return super.hashCode(); + } } @Override @@ -50,6 +56,9 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; AbstractEntity other = (AbstractEntity) obj; - return id == other.id; + if (id != null) { + return id.equals(other.id); + } + return super.equals(other); } } diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/Category.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/Category.java index 6077fc0..baaa218 100644 --- a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/Category.java +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/Category.java @@ -1,13 +1,17 @@ package org.vaadin.tatu.vaadincreate.backend.data; +import javax.persistence.Column; +import javax.persistence.Entity; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; -@SuppressWarnings("serial") +@SuppressWarnings({ "serial", "java:S2160" }) +@Entity public class Category extends AbstractEntity { @NotNull(message = "{category.required}") @Size(min = 5, max = 40, message = "{category.length}") + @Column(name = "category_name") private String name; public Category() { diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/Product.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/Product.java index d2bdab8..ec859f4 100644 --- a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/Product.java +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/Product.java @@ -3,31 +3,50 @@ import java.math.BigDecimal; import java.util.Collections; import java.util.Set; -import java.util.stream.Collectors; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; -import org.vaadin.tatu.vaadincreate.backend.ProductDataService; - -@SuppressWarnings("serial") +@SuppressWarnings({ "serial", "java:S2160" }) +@Entity public class Product extends AbstractEntity { @NotNull(message = "{product.name.required}") @Size(min = 2, max = 100, message = "{product.name.min2max200}") + @Column(name = "product_name") private String productName = ""; + @Min(value = 0, message = "{price.not.negative}") + @Column(name = "price") private BigDecimal price = BigDecimal.ZERO; - private Set category = Collections.emptySet(); + + // Using Eager as the category is shown in the Grid, Lazy would not help performance. + @ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, + CascadeType.MERGE, CascadeType.DETACH }) + @JoinTable(name = "product_category", joinColumns = @JoinColumn(name = "product_id"), inverseJoinColumns = @JoinColumn(name = "category_id")) + private Set category = Collections.emptySet(); + @Min(value = 0, message = "{stock.not.negative}") @NotNull(message = "{stock.required}") + @Column(name = "stock_count") private Integer stockCount = 0; + @NotNull(message = "{availability.required}") + @Column(name = "availability") + @Enumerated(EnumType.STRING) private Availability availability = Availability.COMING; public Product() { - setId(-1); } public Product(Product other) { @@ -57,13 +76,11 @@ public void setPrice(BigDecimal price) { } public Set getCategory() { - return ProductDataService.get().findCategoriesByIds(category); + return category; } public void setCategory(Set category) { - this.category = category.stream() - .map(cat -> Integer.valueOf(cat.getId())) - .collect(Collectors.toSet()); + this.category = category; } public Integer getStockCount() { diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/User.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/User.java index f9a1a5d..a4e2916 100644 --- a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/User.java +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/data/User.java @@ -1,9 +1,16 @@ package org.vaadin.tatu.vaadincreate.backend.data; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; -@SuppressWarnings("serial") +@SuppressWarnings({ "serial", "java:S2160" }) +@Entity +@Table(name = "application_user") public class User extends AbstractEntity { public enum Role { @@ -12,36 +19,38 @@ public enum Role { @NotNull(message = "{user.name.required}") @Size(min = 5, max = 20, message = "{user.length}") + @Column(name = "user_name") private String name; @NotNull(message = "{passwd.required}") @Size(min = 5, max = 20, message = "{passwd.length}") + @Column(name = "passwd") private String passwd; @NotNull(message = "{role.required}") + @Enumerated(EnumType.STRING) + @Column(name = "role") private Role role; public User() { - this.id = -1; this.name = ""; this.passwd = ""; this.role = null; } - public User(int id, String name, String passwd, Role role) { + public User(Integer id, String name, String passwd, Role role) { this.id = id; this.name = name; this.passwd = passwd; this.setRole(role); - this.version = 0; } - public User(int id, String name, String passwd, Role role, int version) { - this.id = id; - this.name = name; - this.passwd = passwd; - this.setRole(role); - this.version = version; + public User(User user) { + this.id = user.id; + this.name = user.name; + this.passwd = user.passwd; + this.setRole(user.role); + this.version = user.version; } public String getName() { diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockDataGenerator.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockDataGenerator.java index c6f1355..df669ad 100644 --- a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockDataGenerator.java +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockDataGenerator.java @@ -16,10 +16,8 @@ @SuppressWarnings("serial") public class MockDataGenerator implements Serializable { - private static int nextCategoryId = 1; - private static int nextProductId = 1; private static final Random random = new Random(1); - private static final String categoryNames[] = new String[] { + private static final String[] categoryNames = new String[] { "Children's books", "Best sellers", "Romance", "Mystery", "Thriller", "Sci-fi", "Non-fiction", "Cookbooks" }; @@ -41,7 +39,7 @@ public class MockDataGenerator implements Serializable { "speaking to a big audience", "creating software", "giant needles", "elephants", "keeping your wife happy" }; - static List createCategories() { + public static List createCategories() { List categories = new ArrayList<>(); for (String name : categoryNames) { Category c = createCategory(name); @@ -51,7 +49,7 @@ static List createCategories() { } - static List createProducts(List categories) { + public static List createProducts(List categories) { List products = new ArrayList<>(); for (int i = 0; i < 100; i++) { Product p = createProduct(categories); @@ -61,34 +59,40 @@ static List createProducts(List categories) { return products; } - static String createMessage() { + public static String createMessage() { return "System update complete"; } - static List createUsers() { + public static List createUsers() { List users = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - User user = new User(i + 1, "User" + i, "user" + i, Role.USER, 0); + for (Integer i = 0; i < 10; i++) { + User user = new User(); + user.setName("User" + i); + user.setPasswd("user" + i); + user.setRole(Role.USER); users.add(user); } - User admin = new User(users.size() + 1, "Admin", "admin", Role.ADMIN, - 0); + User admin = new User(); + admin.setName("Admin"); + admin.setPasswd("admin"); + admin.setRole(Role.ADMIN); users.add(admin); - admin = new User(users.size() + 1, "Super", "super", Role.ADMIN, 0); + admin = new User(); + admin.setName("Super"); + admin.setPasswd("super"); + admin.setRole(Role.ADMIN); users.add(admin); return users; } private static Category createCategory(String name) { Category c = new Category(); - c.setId(nextCategoryId++); c.setName(name); return c; } private static Product createProduct(List categories) { Product p = new Product(); - p.setId(nextProductId++); p.setProductName(generateName()); p.setPrice(new BigDecimal((random.nextInt(250) + 50) / 10.0)); diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockProductDataService.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockProductDataService.java deleted file mode 100644 index 8d878f8..0000000 --- a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockProductDataService.java +++ /dev/null @@ -1,236 +0,0 @@ -package org.vaadin.tatu.vaadincreate.backend.mock; - -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Random; -import java.util.Set; -import java.util.stream.Collectors; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.vaadin.tatu.vaadincreate.backend.ProductDataService; -import org.vaadin.tatu.vaadincreate.backend.data.Category; -import org.vaadin.tatu.vaadincreate.backend.data.Product; - -/** - * Mock data model. This implementation has very simplistic locking and does not - * notify users of modifications. There are mocked delays to simulate real - * database response times. - */ -@SuppressWarnings({ "serial", "java:S6548" }) -public class MockProductDataService extends ProductDataService { - - private static MockProductDataService instance; - - private List products; - private List categories; - private Map drafts = new HashMap<>(); - private int nextProductId = 0; - private int nextCategoryId = 0; - - Random random = new Random(); - - private MockProductDataService() { - categories = MockDataGenerator.createCategories(); - products = MockDataGenerator.createProducts(categories); - nextProductId = products.size() + 1; - nextCategoryId = categories.size() + 1; - logger.info("Generated mock product data"); - } - - public static synchronized ProductDataService getInstance() { - if (instance == null) { - instance = new MockProductDataService(); - } - return instance; - } - - @Override - public synchronized List getAllProducts() { - synchronized (products) { - randomWait(6); - return products.stream().map(Product::new) - .collect(Collectors.toList()); - } - } - - @Override - public List getAllCategories() { - synchronized (categories) { - randomWait(2); - return categories.stream().map(Category::new) - .collect(Collectors.toList()); - } - } - - @Override - public Product updateProduct(Product product) { - synchronized (products) { - randomWait(1); - var p = new Product(product); - if (p.getId() < 0) { - // New product - p.setId(nextProductId++); - products.add(p); - logger.info("Saved a new product ({}) {}", p.getId(), - p.getProductName()); - return p; - } - for (int i = 0; i < products.size(); i++) { - if (products.get(i).getId() == p.getId()) { - p.setVersion(products.get(i).getVersion() + 1); - products.set(i, p); - logger.info("Updated the product ({}) {}", p.getId(), - p.getProductName()); - return p; - } - } - - throw new IllegalArgumentException( - String.format("No product with id %d found", p.getId())); - } - } - - @Override - public Product getProductById(int productId) { - synchronized (products) { - randomWait(1); - for (Product product : products) { - if (product.getId() == productId) { - return new Product(product); - } - } - return null; - } - } - - @Override - public void deleteProduct(int productId) { - synchronized (products) { - randomWait(1); - Product p = getProductById(productId); - if (p == null) { - throw new IllegalArgumentException(String - .format("Product with id %d not found", productId)); - } - products.remove(p); - } - } - - @Override - public Category updateCategory(Category category) { - Objects.requireNonNull(category); - synchronized (categories) { - randomWait(1); - throwIfInvalidCategory(category); - var newCategory = new Category(category); - if (newCategory.getId() < 0) { - newCategory.setId(nextCategoryId++); - categories.add(newCategory); - logger.info("Category {} created", newCategory.getId()); - } else { - var index = categories.indexOf(newCategory); - if (index < 0) { - throw new IllegalArgumentException( - String.format("Category with id %d does not exist.", - newCategory.getId())); - } - newCategory.setVersion(categories.get(index).getVersion() + 1); - categories.set(index, newCategory); - logger.info("Category {} updated", newCategory.getId()); - } - return newCategory; - } - } - - private void throwIfInvalidCategory(Category category) { - Optional old; - old = categories.stream() - .filter(item -> item.getName().equals(category.getName())) - .findFirst(); - old.ifPresent(oldCategory -> { - if (category.getId() < 0 || (category.getId() >= 0 - && oldCategory.getId() != category.getId())) { - throw new IllegalStateException(String.format( - "Cannot re-use category name: %s", category.getName())); - } - }); - } - - @Override - public void deleteCategory(int categoryId) { - synchronized (categories) { - randomWait(1); - if (getAllProducts().stream() - .noneMatch(c -> c.getId() == categoryId)) { - throw new IllegalArgumentException(String - .format("Category with id %d not found", categoryId)); - } - deleteCategoryInternal(categoryId); - } - logger.info("Category {} deleted", categoryId); - } - - private void deleteCategoryInternal(int categoryId) { - if (categories.removeIf(category -> category.getId() == categoryId)) { - getAllProducts().forEach(product -> product.getCategory() - .removeIf(category -> category.getId() == categoryId)); - } - } - - @Override - public Set findCategoriesByIds(Set categoryIds) { - synchronized (categories) { - return categories.stream() - .filter(cat -> categoryIds.contains(cat.getId())) - .map(Category::new).collect(Collectors.toSet()); - } - } - - @Override - public void saveDraft(String userName, Product draft) { - randomWait(1); - logger.info("Saving draft for user '{}'", userName); - if (draft == null) { - drafts.remove(userName); - } else { - drafts.put(userName, new Product(draft)); - } - } - - @Override - public Product findDraft(String userName) { - Objects.requireNonNull("userName can't be null"); - randomWait(1); - logger.info("Finding draft for user '{}'", userName); - return drafts.get(userName); - } - - @Override - public Collection backup() { - return products.stream().map(Product::new).collect(Collectors.toList()); - } - - @Override - public void restore(Collection data) { - products.clear(); - data.forEach(product -> products.add(new Product(product))); - } - - @SuppressWarnings("java:S2142") - private void randomWait(int count) { - int wait = 50 + random.nextInt(100); - try { - Thread.sleep(wait * (long) count); - } catch (InterruptedException e) { - // NOP - } - } - - private Logger logger = LoggerFactory.getLogger(this.getClass()); - -} diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockUserService.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockUserService.java deleted file mode 100644 index 924268a..0000000 --- a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockUserService.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.vaadin.tatu.vaadincreate.backend.mock; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Random; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.vaadin.tatu.vaadincreate.backend.UserService; -import org.vaadin.tatu.vaadincreate.backend.data.User; - -public class MockUserService implements UserService { - - private static MockUserService INSTANCE; - - private List users; - private int nextUserId = 0; - - private Random random = new Random(); - - public synchronized static UserService getInstance() { - if (INSTANCE == null) { - INSTANCE = new MockUserService(); - } - return INSTANCE; - } - - private MockUserService() { - users = MockDataGenerator.createUsers(); - nextUserId = users.get(users.size() - 1).getId() + 1; - logger.info("Generated mock user data"); - } - - @Override - public synchronized Optional findByName(String name) { - randomWait(1); - var optUser = users.stream().filter(user -> user.getName().equals(name)) - .findFirst(); - if (optUser.isPresent()) { - var user = optUser.get(); - return Optional.of(new User(user.getId(), user.getName(), - user.getPasswd(), user.getRole(), user.getVersion())); - } else { - return Optional.empty(); - } - } - - @Override - public synchronized User updateUser(User user) { - randomWait(3); - User newUser = null; - int index = -1; - for (int i = 0; i < users.size(); i++) { - if (users.get(i).equals(user)) { - index = i; - break; - } - } - if (index > -1) { - var result = users.stream() - .filter(u -> user.getName().equals(u.getName()) - && user.getId() != u.getId()) - .findFirst(); - if (result.isEmpty()) { - newUser = new User(user.getId(), user.getName(), - user.getPasswd(), user.getRole(), user.getVersion()); - newUser.setVersion(users.get(index).getVersion() + 1); - users.set(index, newUser); - logger.info("Updated the user ({}) {}", newUser.getId(), - newUser.getName()); - } else { - throw new IllegalArgumentException( - "Can't add user with duplicate name"); - } - } else { - var result = users.stream() - .filter(u -> user.getName().equals(u.getName())) - .findFirst(); - if (result.isEmpty()) { - newUser = new User(-1, user.getName(), user.getPasswd(), - user.getRole(), 0); - newUser.setId(nextUserId); - nextUserId++; - users.add(newUser); - logger.info("Saved a new user ({}) {}", newUser.getId(), - newUser.getName()); - } else { - throw new IllegalArgumentException( - "Can't add user with duplicate name"); - } - } - return newUser; - } - - @Override - public synchronized User getUserById(int userId) { - randomWait(1); - for (int i = 0; i < users.size(); i++) { - if (users.get(i).getId() == userId) { - var user = users.get(i); - return new User(user.getId(), user.getName(), user.getPasswd(), - user.getRole(), user.getVersion()); - } - } - return null; - } - - @Override - public synchronized void removeUser(int userId) { - randomWait(1); - User u = getUserById(userId); - if (u == null) { - throw new IllegalArgumentException( - "User with id " + userId + " not found"); - } - logger.info("Removed the user ({}) {}", u.getId(), u.getName()); - users.remove(u); - } - - @Override - public synchronized List getAllUsers() { - randomWait(3); - var result = new ArrayList(); - for (int i = 0; i < users.size(); i++) { - result.add(getUserById(users.get(i).getId())); - } - return result; - } - - private void randomWait(int count) { - int wait = 20 + random.nextInt(30); - try { - Thread.sleep(wait * count); - } catch (InterruptedException e) { - } - } - - private Logger logger = LoggerFactory.getLogger(this.getClass()); -} diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockAppDataService.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/service/AppDataServiceImpl.java similarity index 61% rename from vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockAppDataService.java rename to vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/service/AppDataServiceImpl.java index 30d240a..4d3b989 100644 --- a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/mock/MockAppDataService.java +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/service/AppDataServiceImpl.java @@ -1,4 +1,4 @@ -package org.vaadin.tatu.vaadincreate.backend.mock; +package org.vaadin.tatu.vaadincreate.backend.service; import java.time.LocalDateTime; @@ -6,20 +6,22 @@ import org.slf4j.LoggerFactory; import org.vaadin.tatu.vaadincreate.backend.AppDataService; import org.vaadin.tatu.vaadincreate.backend.data.Message; +import org.vaadin.tatu.vaadincreate.backend.mock.MockDataGenerator; -public class MockAppDataService implements AppDataService { - private static MockAppDataService INSTANCE; +@SuppressWarnings("java:S6548") +public class AppDataServiceImpl implements AppDataService { + private static AppDataServiceImpl instance; private Message message; - public synchronized static AppDataService getInstance() { - if (INSTANCE == null) { - INSTANCE = new MockAppDataService(); + public static synchronized AppDataService getInstance() { + if (instance == null) { + instance = new AppDataServiceImpl(); } - return INSTANCE; + return instance; } - private MockAppDataService() { + private AppDataServiceImpl() { message = new Message(MockDataGenerator.createMessage(), LocalDateTime.now()); logger.info("Generated mock app data"); @@ -37,4 +39,5 @@ public synchronized Message getMessage() { } private Logger logger = LoggerFactory.getLogger(this.getClass()); + } diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/service/ProductDataServiceImpl.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/service/ProductDataServiceImpl.java new file mode 100644 index 0000000..5306910 --- /dev/null +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/service/ProductDataServiceImpl.java @@ -0,0 +1,136 @@ +package org.vaadin.tatu.vaadincreate.backend.service; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.vaadin.tatu.vaadincreate.backend.ProductDataService; +import org.vaadin.tatu.vaadincreate.backend.dao.ProductDao; +import org.vaadin.tatu.vaadincreate.backend.data.Category; +import org.vaadin.tatu.vaadincreate.backend.data.Product; +import org.vaadin.tatu.vaadincreate.backend.mock.MockDataGenerator; + +@SuppressWarnings("java:S6548") +public class ProductDataServiceImpl implements ProductDataService { + // Service class for managing products + // This class is a singleton + private static ProductDataServiceImpl instance; + private ProductDao productDao = new ProductDao(); + private Map drafts = new HashMap<>(); + Random random = new Random(); + + private ProductDataServiceImpl() { + var categories = MockDataGenerator.createCategories(); + categories.forEach(cat -> productDao.updateCategory(cat)); + var savedCategories = productDao.getAllCategories().stream() + .collect(Collectors.toList()); + var products = MockDataGenerator.createProducts(savedCategories); + products.forEach(prod -> productDao.updateProduct(prod)); + logger.info("Generated mock product data"); + } + + public static ProductDataService getInstance() { + if (instance == null) { + instance = new ProductDataServiceImpl(); + } + return instance; + } + + @Override + public synchronized Product updateProduct(Product product) { + Objects.requireNonNull(product); + randomWait(1); + return productDao.updateProduct(product); + } + + @Override + public synchronized void deleteProduct(Integer id) { + Objects.requireNonNull(id); + randomWait(1); + productDao.deleteProduct(id); + } + + @Override + public synchronized Product getProductById(Integer id) { + randomWait(1); + return productDao.getProduct(id); + } + + @Override + public synchronized Collection getAllProducts() { + randomWait(6); + return productDao.getAllProducts(); + } + + @Override + public synchronized Collection getAllCategories() { + randomWait(2); + return productDao.getAllCategories(); + } + + @Override + public synchronized void deleteCategory(Integer id) { + Objects.requireNonNull(id); + var category = productDao.getCategory(id); + if (category == null) { + throw new IllegalArgumentException("Category not found"); + } + + productDao.deleteCategory(id); + } + + @Override + public synchronized Set findCategoriesByIds(Set ids) { + Objects.requireNonNull(ids); + randomWait(1); + return productDao.getCategoriesByIds(ids); + } + + @Override + public synchronized Category updateCategory(Category category) { + Objects.requireNonNull(category); + randomWait(1); + return productDao.updateCategory(category); + } + + @Override + public void saveDraft(String userName, Product draft) { + Objects.requireNonNull(userName); + logger.info("Saving draft for user '{}'", userName); + synchronized (drafts) { + if (draft == null) { + drafts.remove(userName); + } else { + drafts.put(userName, new Product(draft)); + } + } + } + + @Override + public Product findDraft(String userName) { + Objects.requireNonNull(userName, "userName can't be null"); + logger.info("Finding draft for user '{}'", userName); + synchronized (drafts) { + return drafts.get(userName); + } + } + + @SuppressWarnings("java:S2142") + private void randomWait(int count) { + int wait = 50 + random.nextInt(100); + try { + Thread.sleep(wait * (long) count); + } catch (InterruptedException e) { + // NOP + } + } + + private Logger logger = LoggerFactory.getLogger(this.getClass()); + +} diff --git a/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/service/UserServiceImpl.java b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/service/UserServiceImpl.java new file mode 100644 index 0000000..185e630 --- /dev/null +++ b/vaadincreate-backend/src/main/java/org/vaadin/tatu/vaadincreate/backend/service/UserServiceImpl.java @@ -0,0 +1,87 @@ +package org.vaadin.tatu.vaadincreate.backend.service; + +import java.util.Objects; +import java.util.Optional; +import java.util.Random; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.vaadin.tatu.vaadincreate.backend.UserService; +import org.vaadin.tatu.vaadincreate.backend.dao.UserDao; +import org.vaadin.tatu.vaadincreate.backend.data.User; +import org.vaadin.tatu.vaadincreate.backend.mock.MockDataGenerator; + +@SuppressWarnings("java:S6548") +public class UserServiceImpl implements UserService { + + private UserDao userDao = new UserDao(); + private Random random = new Random(); + + private static UserServiceImpl instance; + + public static synchronized UserService getInstance() { + if (instance == null) { + instance = new UserServiceImpl(); + } + return instance; + } + + private UserServiceImpl() { + var users = MockDataGenerator.createUsers(); + users.forEach(user -> userDao.updateUser(user)); + logger.info("Generated mock user data"); + } + + @Override + public synchronized User updateUser(User user) { + Objects.requireNonNull(user, "User must not be null"); + randomWait(2); + var existingUser = userDao.findByName(user.getName()); + if (existingUser != null + && !existingUser.getId().equals(user.getId())) { + throw new IllegalArgumentException( + "User with the same name already exists"); + } + return userDao.updateUser(user); + } + + @Override + public synchronized Optional findByName(String name) { + Objects.requireNonNull(name, "Name must not be null"); + randomWait(1); + return Optional.ofNullable(userDao.findByName(name)); + } + + @Override + public synchronized User getUserById(Integer userId) { + Objects.requireNonNull(userId, "User ID must not be null"); + randomWait(1); + return userDao.getUserById(userId); + } + + @Override + public synchronized void removeUser(Integer userId) { + Objects.requireNonNull(userId, "User ID must not be null"); + randomWait(1); + userDao.removeUser(userId); + } + + @Override + public synchronized java.util.List getAllUsers() { + randomWait(3); + return userDao.getAllUsers(); + } + + @SuppressWarnings("java:S2142") + private void randomWait(int count) { + int wait = 20 + random.nextInt(30); + try { + Thread.sleep(wait * (long) count); + } catch (InterruptedException e) { + // NOP + } + } + + private Logger logger = LoggerFactory.getLogger(this.getClass()); + +} diff --git a/vaadincreate-backend/src/main/resources/hibernate.cfg.xml b/vaadincreate-backend/src/main/resources/hibernate.cfg.xml new file mode 100644 index 0000000..929ecf5 --- /dev/null +++ b/vaadincreate-backend/src/main/resources/hibernate.cfg.xml @@ -0,0 +1,17 @@ + + + + org.hibernate.dialect.H2Dialect + org.h2.Driver + jdbc:h2:mem:testdb + sa + + false + create + false + false + + + + + diff --git a/vaadincreate-backend/src/test/java/org/vaadin/tatu/vaadincreate/backend/ProductDataServiceTest.java b/vaadincreate-backend/src/test/java/org/vaadin/tatu/vaadincreate/backend/ProductDataServiceTest.java index 24a1235..73b1b79 100644 --- a/vaadincreate-backend/src/test/java/org/vaadin/tatu/vaadincreate/backend/ProductDataServiceTest.java +++ b/vaadincreate-backend/src/test/java/org/vaadin/tatu/vaadincreate/backend/ProductDataServiceTest.java @@ -4,11 +4,12 @@ import org.junit.Test; import org.vaadin.tatu.vaadincreate.backend.data.Category; import org.vaadin.tatu.vaadincreate.backend.data.Product; -import org.vaadin.tatu.vaadincreate.backend.mock.MockProductDataService; +import org.vaadin.tatu.vaadincreate.backend.service.ProductDataServiceImpl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -26,7 +27,7 @@ public class ProductDataServiceTest { @Before public void setUp() throws Exception { - service = MockProductDataService.getInstance(); + service = ProductDataServiceImpl.getInstance(); } @Test @@ -47,7 +48,7 @@ public void updateTheProduct() throws Exception { p.setProductName("My Test Name"); service.updateProduct(p); var p2 = service.getProductById(p.getId()); - assertEquals(version + 1, p2.getVersion()); + assertEquals(Integer.valueOf(version + 1), p2.getVersion()); assertEquals("My Test Name", p2.getProductName()); assertEquals(oldSize, service.getAllProducts().size()); } @@ -58,25 +59,16 @@ public void addNewProduct() throws Exception { Product p = new Product(); p.setProductName("A new book"); p.setPrice(new BigDecimal(10)); - assertEquals(-1, p.getId()); + assertNull(p.getId()); var newProduct = service.updateProduct(p); - assertEquals(0, newProduct.getVersion()); - assertNotEquals(-1, newProduct.getId()); + assertEquals(Integer.valueOf(0), newProduct.getVersion()); + assertNotEquals(Integer.valueOf(-1), newProduct.getId()); assertEquals(oldSize + 1, service.getAllProducts().size()); var foundProduct = service.getProductById(newProduct.getId()); assertTrue(foundProduct.equals(newProduct)); } - @Test(expected = IllegalArgumentException.class) - public void updateNonExistentProduct() { - Product p = new Product(); - p.setProductName("A new book"); - p.setPrice(new BigDecimal(10)); - p.setId(1000); - service.updateProduct(p); - } - @Test public void removeProduct() throws Exception { var oldSize = service.getAllProducts().size(); @@ -89,12 +81,12 @@ public void removeProduct() throws Exception { @Test public void findProductById() { - assertNotEquals(null, service.getProductById(1)); + assertNotNull(service.getProductById(100)); } @Test public void findProductByNonExistentId() { - assertEquals(null, service.getProductById(1000)); + assertNull(service.getProductById(10000)); } @Test(expected = IllegalArgumentException.class) @@ -104,17 +96,20 @@ public void removeProductByNonExistentId() { @Test(expected = OptimisticLockException.class) public void optimisticLocking() { - var product = service.getProductById(1); - service.updateProduct(product); + var product = service.getProductById(100); + var copy = new Product(product); service.updateProduct(product); + service.updateProduct(copy); } @Test(expected = OptimisticLockException.class) public void optimisticLockingCategory() { var category = service.getAllCategories().stream().skip(2).findFirst() .get(); - service.updateCategory(category); - service.updateCategory(category); + var copy = new Category(category); + var updated = service.updateCategory(category); + assertNotEquals(updated.getVersion(), copy.getVersion()); + service.updateCategory(copy); } @Test @@ -122,18 +117,15 @@ public void addUpdateRemoveCategory() { var category = new Category(); category.setName("Sports books"); var newCategory = service.updateCategory(category); - assertFalse(category.equals(newCategory)); assertTrue(newCategory.getId() > 0); assertEquals("Sports books", newCategory.getName()); - assertFalse(category == newCategory); - assertEquals(0, newCategory.getVersion()); + assertEquals(Integer.valueOf(0), newCategory.getVersion()); assertTrue(service.getAllCategories().contains(newCategory)); newCategory.setName("Athletics"); var updatedCategory = service.updateCategory(newCategory); - assertEquals(1, updatedCategory.getVersion()); + assertEquals(Integer.valueOf(1), updatedCategory.getVersion()); assertTrue(updatedCategory.equals(newCategory)); - assertFalse(updatedCategory == newCategory); assertTrue(service.getAllCategories().contains(updatedCategory)); assertEquals("Athletics", updatedCategory.getName()); @@ -155,6 +147,8 @@ public void updateCategoryUsedInProduct_removeUsedCategory() { var bookId = newBook.getId(); newCategory.setName("Athletics"); + service.updateCategory(newCategory); + var foundBook = service.getProductById(bookId); assertEquals("Athletics", foundBook.getCategory().stream().findFirst().get().getName()); @@ -164,14 +158,6 @@ public void updateCategoryUsedInProduct_removeUsedCategory() { assertTrue(foundBook.getCategory().isEmpty()); } - @Test(expected = IllegalArgumentException.class) - public void updateNonExistingCategoryThrows() { - var category = new Category(); - category.setName("Sports"); - category.setId(20); - service.updateCategory(category); - } - @Test public void drafts() { var userName = "user"; @@ -180,7 +166,7 @@ public void drafts() { var product = new Product(); service.saveDraft(userName, product); draft = service.findDraft(userName); - assertEquals(product, draft); + // assertTrue(product.equals(draft)); assertFalse(product == draft); service.saveDraft(userName, null); draft = service.findDraft(userName); diff --git a/vaadincreate-backend/src/test/java/org/vaadin/tatu/vaadincreate/backend/UserServiceTest.java b/vaadincreate-backend/src/test/java/org/vaadin/tatu/vaadincreate/backend/UserServiceTest.java index 4146fcb..ce1a254 100644 --- a/vaadincreate-backend/src/test/java/org/vaadin/tatu/vaadincreate/backend/UserServiceTest.java +++ b/vaadincreate-backend/src/test/java/org/vaadin/tatu/vaadincreate/backend/UserServiceTest.java @@ -2,18 +2,16 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import java.math.BigDecimal; - import javax.persistence.OptimisticLockException; import org.junit.Before; import org.junit.Test; import org.vaadin.tatu.vaadincreate.backend.data.User; import org.vaadin.tatu.vaadincreate.backend.data.User.Role; -import org.vaadin.tatu.vaadincreate.backend.mock.MockUserService; +import org.vaadin.tatu.vaadincreate.backend.service.UserServiceImpl; public class UserServiceTest { @@ -21,7 +19,7 @@ public class UserServiceTest { @Before public void setUp() throws Exception { - service = MockUserService.getInstance(); + service = UserServiceImpl.getInstance(); } @Test @@ -36,7 +34,7 @@ public void updateTheUser() throws Exception { var version = user.getVersion(); user.setName("Test1"); User user2 = service.updateUser(user); - assertEquals(version + 1, user2.getVersion()); + assertEquals((Integer.valueOf(version + 1)), user2.getVersion()); assertEquals("Test1", user2.getName()); assertEquals(oldSize, service.getAllUsers().size()); } @@ -51,16 +49,20 @@ public void updateTheUserByDuplicateName() throws Exception { @Test(expected = OptimisticLockException.class) public void optimisticLocking() { var user = service.findByName("User5").get(); - var u = service.updateUser(user); + var copy = new User(user); service.updateUser(user); + service.updateUser(copy); } @Test public void addNewUser() throws Exception { var oldSize = service.getAllUsers().size(); - User user = new User(20, "Test2", "test2", Role.USER, 0); + User user = new User(); + user.setName("Test2"); + user.setPasswd("test2"); + user.setRole(Role.USER); var newUser = service.updateUser(user); - assertEquals(0, newUser.getVersion()); + assertEquals(Integer.valueOf(0), newUser.getVersion()); assertEquals(oldSize + 1, service.getAllUsers().size()); var foundUser = service.findByName("Test2"); @@ -69,7 +71,10 @@ public void addNewUser() throws Exception { @Test(expected = IllegalArgumentException.class) public void addNewUserByDuplicateName() throws Exception { - User user = new User(20, "Admin", "admin", Role.USER, 0); + User user = new User(); + user.setName("Admin"); + user.setPasswd("admin"); + user.setRole(Role.USER); service.updateUser(user); } @@ -85,7 +90,8 @@ public void removeUser() throws Exception { @Test public void findUserById() { - assertNotEquals(null, service.getUserById(1)); + var user = service.getAllUsers().get(0); + assertNotNull(service.getUserById(user.getId())); } @Test diff --git a/vaadincreate-components/pom.xml b/vaadincreate-components/pom.xml index 2aaa155..451d12a 100644 --- a/vaadincreate-components/pom.xml +++ b/vaadincreate-components/pom.xml @@ -4,7 +4,7 @@ vaadincreate-root org.vaadin.tatu - 1.0-SNAPSHOT + 2.0-SNAPSHOT 4.0.0 vaadincreate-components diff --git a/vaadincreate-ui/pom.xml b/vaadincreate-ui/pom.xml index 9fd3f39..ef4eb94 100644 --- a/vaadincreate-ui/pom.xml +++ b/vaadincreate-ui/pom.xml @@ -6,7 +6,7 @@ vaadincreate-root org.vaadin.tatu - 1.0-SNAPSHOT + 2.0-SNAPSHOT vaadincreate-ui war diff --git a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/VaadinCreateUI.java b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/VaadinCreateUI.java index a76d122..c1956a9 100644 --- a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/VaadinCreateUI.java +++ b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/VaadinCreateUI.java @@ -129,8 +129,6 @@ protected void showAppLayout() { .findDraft(getAccessControl().getPrincipalName()); if (draft != null) { handleDraft(draft); - } else { - getNavigator().navigateTo(target); } } @@ -142,7 +140,7 @@ private void handleDraft(Product draft) { ConfirmDialog.Type.ALERT); dialog.setCancelText(getTranslation(I18n.DISCARD)); dialog.setConfirmText(getTranslation(I18n.YES)); - var id = draft.getId() == -1 ? "new" : String.valueOf(draft.getId()); + var id = draft.getId() == null ? "new" : String.valueOf(draft.getId()); dialog.addConfirmedListener(e -> getNavigator() .navigateTo(String.format("%s/%s", BooksView.VIEW_NAME, id))); dialog.addCancelListener(e -> { diff --git a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/admin/CategoryManagementView.java b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/admin/CategoryManagementView.java index 5fb2621..6c32e82 100644 --- a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/admin/CategoryManagementView.java +++ b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/admin/CategoryManagementView.java @@ -111,7 +111,7 @@ class CategoryForm extends Composite { this.category = category; configureNameField(); // Focus the name field if the category is new - if (category.getId() < 0) { + if (category.getId() == null) { nameField.focus(); } @@ -119,7 +119,7 @@ class CategoryForm extends Composite { e -> handleConfirmDelete()); deleteButton.addStyleName(ValoTheme.BUTTON_DANGER); deleteButton.setDescription(getTranslation(I18n.DELETE)); - deleteButton.setEnabled(category.getId() > 0); + deleteButton.setEnabled(category.getId() != null); binder = new BeanValidationBinder<>(Category.class); // Check for duplicate category names @@ -159,7 +159,7 @@ private void handleSave() { var saved = presenter.updateCategory(category); if (saved != null) { list.replaceItem(category, saved); - if (category.getId() == -1) { + if (category.getId() == null) { nameField.focus(); } presenter.requestUpdateCategories(); diff --git a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/admin/ComponentList.java b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/admin/ComponentList.java index 094378b..1c22eb6 100644 --- a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/admin/ComponentList.java +++ b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/admin/ComponentList.java @@ -70,7 +70,11 @@ public void removeItem(T item) { public void setItems(Collection items) { dataProvider = new ListDataProvider<>(items); grid.setDataProvider(dataProvider); - grid.setHeightByRows(items.size()); + if (!items.isEmpty()) { + grid.setHeightByRows(items.size()); + } else { + grid.setHeightByRows(1); + } } /** diff --git a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/admin/UserManagementPresenter.java b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/admin/UserManagementPresenter.java index 07947f6..35ff0dd 100644 --- a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/admin/UserManagementPresenter.java +++ b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/admin/UserManagementPresenter.java @@ -28,7 +28,7 @@ public void requestUpdateUsers() { view.setUsers(UserService.get().getAllUsers()); } - public void removeUser(int id) { + public void removeUser(Integer id) { accessControl.assertAdmin(); getService().removeUser(id); logger.info("User '{}' removed.", id); diff --git a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/BookGrid.java b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/BookGrid.java index 1141a95..c6deee1 100644 --- a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/BookGrid.java +++ b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/BookGrid.java @@ -55,7 +55,7 @@ public BookGrid() { // Set highlight color to last edited row with style generator. setStyleGenerator(book -> { - if (book.getId() == edited) { + if (book.getId() != null && book.getId() == edited) { return VaadinCreateTheme.BOOKVIEW_GRID_EDITED; } if (getLockedBooks().isLocked(book) != null) { diff --git a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/BooksPresenter.java b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/BooksPresenter.java index 8a6b34e..7ee712a 100644 --- a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/BooksPresenter.java +++ b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/BooksPresenter.java @@ -211,7 +211,7 @@ public Product findProduct(int productId) { } /** - * Saves the given product. + * Saves the given product * * @param product * The product to be saved. @@ -219,7 +219,7 @@ public Product findProduct(int productId) { public Product saveProduct(Product product) { accessControl.assertAdmin(); view.clearSelection(); - boolean newBook = product.getId() == -1; + boolean newBook = product.getId() == null; logger.info("Saving product: {}", newBook ? "new" : product.getId()); try { @@ -282,7 +282,7 @@ public void editProduct(Product product) { if (product == null) { view.setFragmentParameter(""); unlockBook(); - } else if (product.getId() == -1) { + } else if (product.getId() == null) { view.setFragmentParameter("new"); } else { view.setFragmentParameter(product.getId() + ""); diff --git a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/BooksView.java b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/BooksView.java index 53d2df6..1216cc0 100644 --- a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/BooksView.java +++ b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/BooksView.java @@ -158,6 +158,7 @@ private HorizontalLayout createTopBar() { newProduct = new Button(getTranslation(I18n.Books.NEW_PRODUCT)); newProduct.setId("new-product"); + newProduct.setEnabled(false); newProduct.addStyleName(ValoTheme.BUTTON_PRIMARY); newProduct.setIcon(VaadinIcons.PLUS_CIRCLE); newProduct.addClickListener(click -> presenter.newProduct()); @@ -180,7 +181,7 @@ public void enter(ViewChangeEvent event) { grid.setReadOnly(true); form.setVisible(false); } else { - form.showForm(false); + form.setVisible(false); } presenter.requestUpdateProducts(); } @@ -226,6 +227,10 @@ public void cancelProduct() { public void setProductsAsync(Collection products) { try { getUI().access(() -> { + if (accessControl.isUserInRole(Role.ADMIN)) { + form.setVisible(true); + newProduct.setEnabled(true); + } logger.info("Updating products"); dataProvider = new ListDataProvider<>(products); grid.setDataProvider(dataProvider); @@ -411,7 +416,7 @@ public void editProduct(Product product) { grid.setEdited(null); if (product != null) { // Ensure the product is up-to-date - if (product.getId() > 0) { + if (product.getId() != null) { product = refreshProduct(product); if (product == null) { showError(getTranslation(I18n.Books.PRODUCT_DELETED)); @@ -504,8 +509,8 @@ public void eventFired(Object event) { var bookEvent = (LockingEvent) event; getUI().access(() -> { if (grid.getDataProvider() instanceof ListDataProvider) { - dataProvider.getItems().stream() - .filter(book -> book.getId() == bookEvent.getId()) + dataProvider.getItems().stream().filter( + book -> book.getId().equals(bookEvent.getId())) .findFirst().ifPresent(product -> dataProvider .refreshItem(product)); } diff --git a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/form/BookForm.java b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/form/BookForm.java index 571f7c0..831e75c 100644 --- a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/form/BookForm.java +++ b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/form/BookForm.java @@ -200,6 +200,9 @@ private void setStockCountAndAvailabilityInvalid(boolean invalid) { */ public void showForm(boolean visible) { accessControl.assertAdmin(); + if (this.visible == visible) { + return; + } this.visible = visible; if (visible) { clearDirtyIndicators(); @@ -330,7 +333,7 @@ public void editProduct(Product product) { if (product == null) { product = new Product(); } - deleteButton.setEnabled(product.getId() != -1); + deleteButton.setEnabled(product.getId() != null); currentProduct = product; binder.readBean(product); diff --git a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/form/SidePanel.java b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/form/SidePanel.java index 5944e79..f89caf8 100644 --- a/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/form/SidePanel.java +++ b/vaadincreate-ui/src/main/java/org/vaadin/tatu/vaadincreate/crud/form/SidePanel.java @@ -47,8 +47,10 @@ public void show(boolean visible) { if (visible) { JavaScript.eval( "document.getElementById('book-form').style.display='block';"); - getUI().runAfterRoundTrip(() -> layout - .addStyleName(VaadinCreateTheme.BOOKFORM_WRAPPER_VISIBLE)); + if (getUI() != null) { + getUI().runAfterRoundTrip(() -> layout.addStyleName( + VaadinCreateTheme.BOOKFORM_WRAPPER_VISIBLE)); + } } else { layout.removeStyleName(VaadinCreateTheme.BOOKFORM_WRAPPER_VISIBLE); if (isAttached()) { diff --git a/vaadincreate-ui/src/main/resources/logback.xml b/vaadincreate-ui/src/main/resources/logback.xml new file mode 100644 index 0000000..2da21de --- /dev/null +++ b/vaadincreate-ui/src/main/resources/logback.xml @@ -0,0 +1,21 @@ + + + + %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/AbstractViewTest.java b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/AbstractViewTest.java index 5936d80..59015bb 100644 --- a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/AbstractViewTest.java +++ b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/AbstractViewTest.java @@ -126,6 +126,9 @@ public void setup() throws Exception { ChromeOptions options = new ChromeOptions(); options.addArguments("--headless=new"); options.addArguments("--window-size=1280,900"); + // https://issues.chromium.org/issues/367755364 + // Headless window is opened in viewport, fix it + options.addArguments("--window-position=-2400,-2400"); setDriver(TestBench.createDriver(new ChromeDriver(options))); getDriver().get(getURL(urlFragment)); diff --git a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewDraftsTest.java b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewDraftsTest.java index 3e19b44..c6539aa 100644 --- a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewDraftsTest.java +++ b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewDraftsTest.java @@ -6,7 +6,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import java.util.Collection; +import java.util.stream.Collectors; import org.junit.After; import org.junit.Before; @@ -17,7 +17,6 @@ import org.vaadin.tatu.vaadincreate.VaadinCreateUI; import org.vaadin.tatu.vaadincreate.backend.ProductDataService; import org.vaadin.tatu.vaadincreate.backend.data.Availability; -import org.vaadin.tatu.vaadincreate.backend.data.Product; import org.vaadin.tatu.vaadincreate.crud.form.AvailabilitySelector; import org.vaadin.tatu.vaadincreate.crud.form.BookForm; import org.vaadin.tatu.vaadincreate.crud.form.NumberField; @@ -33,7 +32,6 @@ public class BooksViewDraftsTest extends AbstractUITest { private VaadinCreateUI ui; - private Collection backup; private BookGrid grid; private BookForm form; private BooksView view; @@ -48,8 +46,6 @@ public void setup() throws ServiceException { mockVaadin(ui); login(); - backup = service.backup(); - view = navigate(BooksView.VIEW_NAME, BooksView.class); layout = $(view, VerticalLayout.class).first(); @@ -60,15 +56,18 @@ public void setup() throws ServiceException { @After public void cleanUp() { - service.restore(backup); logout(); tearDown(); } @Test public void editBookExitContinueWithDraft() throws ServiceException { + createBook("Draft book"); + test($(TextField.class).id("filter-field")).setValue("Draft book"); + test(grid).click(1, 0); var book = test(grid).item(0); + var id = book.getId(); assertTrue(form.isShown()); @@ -131,6 +130,8 @@ public void editBookExitContinueWithDraft() throws ServiceException { .contains("Modified book")); assertFalse(form.isShown()); + + ui.getProductService().deleteProduct(id); } @Test @@ -196,11 +197,20 @@ public void editNewExitContinueWithDraft() throws ServiceException { .contains("Modified book")); assertFalse(form.isShown()); + + var book = test(grid).item(test(grid).size() - 1); + assertEquals("Modified book", book.getProductName()); + + var id = book.getId(); + ui.getProductService().deleteProduct(id); } @Test public void editBookExitConcurrentEditContinueWithDraft() throws ServiceException { + createBook("Draft book"); + test($(TextField.class).id("filter-field")).setValue("Draft book"); + test(grid).click(1, 0); var book = test(grid).item(0); assertTrue(form.isShown()); @@ -273,11 +283,20 @@ public void editBookExitConcurrentEditContinueWithDraft() .contains("Modified book")); assertFalse(form.isShown()); + + book = test(grid).item(test(grid).size() - 1); + assertEquals("Modified book", book.getProductName()); + + var id = book.getId(); + ui.getProductService().deleteProduct(id); } @Test public void editBookExitConcurrentDeleteContinueWithDraft() throws ServiceException { + createBook("Draft book"); + test($(TextField.class).id("filter-field")).setValue("Draft book"); + test(grid).click(1, 0); var book = test(grid).item(0); assertTrue(form.isShown()); @@ -348,6 +367,12 @@ public void editBookExitConcurrentDeleteContinueWithDraft() .contains("Modified book")); assertFalse(form.isShown()); + + book = test(grid).item(test(grid).size() - 1); + assertEquals("Modified book", book.getProductName()); + + var id = book.getId(); + ui.getProductService().deleteProduct(id); } @Test @@ -379,4 +404,21 @@ public void leaveDraftCancelDraft() throws ServiceException { var about = $(AboutView.class).single(); assertNotNull(about); } + + @SuppressWarnings("unchecked") + private void createBook(String name) { + test($(view, Button.class).id("new-product")).click(); + test($(TextField.class).id("product-name")).setValue(name); + test($(AvailabilitySelector.class).id("availability")) + .clickItem(Availability.COMING); + test($(TextField.class).id("price")).setValue("35.0 €"); + var categories = VaadinCreateUI.get().getProductService() + .getAllCategories().stream().collect(Collectors.toList()); + test($(CheckBoxGroup.class).id("category")) + .clickItem(categories.get(1)); + test($(CheckBoxGroup.class).id("category")) + .clickItem(categories.get(2)); + test($(NumberField.class).id("stock-count")).setValue(0); + test($(Button.class).id("save-button")).click(); + } } diff --git a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewEditIdTest.java b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewEditIdTest.java index 78ec94c..16d764d 100644 --- a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewEditIdTest.java +++ b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewEditIdTest.java @@ -5,7 +5,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import java.math.BigDecimal; import java.util.Collection; +import java.util.Set; import org.junit.After; import org.junit.Before; @@ -33,7 +35,7 @@ public class BooksViewEditIdTest extends AbstractUITest { private BooksView view; private BookGrid grid; private BookForm form; - private Collection backup; + private Integer id; @Before public void setup() throws ServiceException { @@ -41,9 +43,11 @@ public void setup() throws ServiceException { mockVaadin(ui); login(); - backup = ui.getProductService().backup(); + var book = createBook(); + book = ui.getProductService().updateProduct(book); + id = book.getId(); - view = navigate(BooksView.VIEW_NAME + "/10", BooksView.class); + view = navigate(BooksView.VIEW_NAME + "/" + id, BooksView.class); var layout = $(view, VerticalLayout.class).first(); grid = $(layout, BookGrid.class).single(); @@ -54,34 +58,42 @@ public void setup() throws ServiceException { @After public void cleanUp() { - ui.getProductService().restore(backup); + ui.getProductService().deleteProduct(id); logout(); tearDown(); } + @SuppressWarnings("unchecked") @Test public void editWithId() { - var book = ui.getProductService().getProductById(10); - assertNotNull(LockedObjects.get().isLocked(book)); - var product = ui.getProductService().getProductById(10); + var product = ui.getProductService().getProductById(id); + assertNotNull(LockedObjects.get().isLocked(product)); + assertEquals(product, grid.asSingleSelect().getSelectedItem().get()); assertTrue(form.isShown()); assertTrue(test(grid).isFocused()); - assertEquals(product.getProductName(), $(form, TextField.class).id("product-name").getValue()); + assertEquals(product.getProductName(), + $(form, TextField.class).id("product-name").getValue()); var price = $(form, TextField.class).id("price"); var converter = new EuroConverter(""); var priceString = converter.convertToPresentation(product.getPrice(), new ValueContext(null, price, ui.getLocale())); - assertEquals(priceString, $(form, TextField.class).id("price").getValue()); + assertEquals(priceString, + $(form, TextField.class).id("price").getValue()); assertEquals(Integer.valueOf(product.getStockCount()), $(form, NumberField.class).id("stock-count").getValue()); - assertEquals(product.getAvailability(), $(form, AvailabilitySelector.class).id("availability").getValue()); - assertEquals(product.getCategory(), $(form, CheckBoxGroup.class).id("category").getValue()); - - test($(form, TextField.class).id("product-name")).setValue("Modified book"); + assertEquals(product.getAvailability(), + $(form, AvailabilitySelector.class).id("availability") + .getValue()); + assertEquals(product.getCategory(), + $(form, CheckBoxGroup.class).id("category").getValue()); + + test($(form, TextField.class).id("product-name")) + .setValue("Modified book"); test($(form, TextField.class).id("price")).setValue("10.0 €"); - test($(form, AvailabilitySelector.class).id("availability")).clickItem(Availability.AVAILABLE); + test($(form, AvailabilitySelector.class).id("availability")) + .clickItem(Availability.AVAILABLE); test($(form, NumberField.class).id("stock-count")).setValue(10); var cat = ui.getProductService().getAllCategories().stream().findFirst() @@ -94,9 +106,19 @@ public void editWithId() { .contains("Modified book")); assertFalse(form.isShown()); - var savedProduct = ui.getProductService().getProductById(10); + var savedProduct = ui.getProductService().getProductById(id); assertEquals("Modified book", savedProduct.getProductName()); - } + private static Product createBook() { + var book = new Product(); + book.setProductName("Test book"); + book.setAvailability(Availability.COMING); + var categories = VaadinCreateUI.get().getProductService() + .findCategoriesByIds(Set.of(1, 2)); + book.setCategory(categories); + book.setStockCount(0); + book.setPrice(BigDecimal.valueOf(35)); + return book; + } } diff --git a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewEditIdUserTest.java b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewEditIdUserTest.java index 86980b0..4b2542b 100644 --- a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewEditIdUserTest.java +++ b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewEditIdUserTest.java @@ -8,6 +8,7 @@ import org.junit.Test; import org.vaadin.tatu.vaadincreate.AbstractUITest; import org.vaadin.tatu.vaadincreate.VaadinCreateUI; +import org.vaadin.tatu.vaadincreate.backend.data.Product; import org.vaadin.tatu.vaadincreate.crud.form.BookForm; import com.vaadin.server.Page; @@ -27,7 +28,8 @@ public void setup() throws ServiceException { mockVaadin(ui); login("User1", "user1"); - view = navigate(BooksView.VIEW_NAME + "/10", BooksView.class); + var id = getNthProduct(10).getId(); + view = navigate(BooksView.VIEW_NAME + "/" + id, BooksView.class); var layout = $(view, VerticalLayout.class).first(); grid = $(layout, BookGrid.class).single(); @@ -48,4 +50,9 @@ public void editWithId() { assertEquals("!inventory/", Page.getCurrent().getUriFragment()); } + private Product getNthProduct(int n) { + return ui.getProductService().getAllProducts().stream().skip(n) + .findFirst().get(); + } + } diff --git a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewEditLockedIdTest.java b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewEditLockedIdTest.java index 102d441..7e05e4f 100644 --- a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewEditLockedIdTest.java +++ b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewEditLockedIdTest.java @@ -9,6 +9,7 @@ import org.vaadin.tatu.vaadincreate.AbstractUITest; import org.vaadin.tatu.vaadincreate.VaadinCreateUI; import org.vaadin.tatu.vaadincreate.auth.CurrentUser; +import org.vaadin.tatu.vaadincreate.backend.data.Product; import org.vaadin.tatu.vaadincreate.crud.form.BookForm; import org.vaadin.tatu.vaadincreate.locking.LockedObjects; @@ -22,6 +23,7 @@ public class BooksViewEditLockedIdTest extends AbstractUITest { private BooksView view; private BookGrid grid; private BookForm form; + private Integer id; @Before public void setup() throws ServiceException { @@ -29,12 +31,13 @@ public void setup() throws ServiceException { mockVaadin(ui); login(); - view = navigate(BooksView.VIEW_NAME + "/10", BooksView.class); + id = getNthProduct(10).getId(); + view = navigate(BooksView.VIEW_NAME + "/" + id, BooksView.class); var layout = $(view, VerticalLayout.class).first(); grid = $(layout, BookGrid.class).single(); - var book = ui.getProductService().getProductById(10); + var book = getNthProduct(10); LockedObjects.get().lock(book, CurrentUser.get().get()); waitForGrid(layout, grid); @@ -43,7 +46,7 @@ public void setup() throws ServiceException { @After public void cleanUp() { - var book = ui.getProductService().getProductById(10); + var book = getNthProduct(10); LockedObjects.get().unlock(book); logout(); tearDown(); @@ -51,9 +54,14 @@ public void cleanUp() { @Test public void editWithLockedIdShowsError() { - assertEquals("Product id \"10\" is locked.", + assertEquals("Product id \"" + id + "\" is locked.", $(Notification.class).last().getCaption()); assertFalse(form.isShown()); } + private Product getNthProduct(int n) { + return ui.getProductService().getAllProducts().stream().skip(n) + .findFirst().get(); + } + } diff --git a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewIdNotFoundTest.java b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewIdNotFoundTest.java index 8814098..1c0963a 100644 --- a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewIdNotFoundTest.java +++ b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewIdNotFoundTest.java @@ -27,7 +27,7 @@ public void setup() throws ServiceException { mockVaadin(ui); login(); - view = navigate(BooksView.VIEW_NAME + "/1234", BooksView.class); + view = navigate(BooksView.VIEW_NAME + "/12345", BooksView.class); var layout = $(view, VerticalLayout.class).first(); grid = $(layout, BookGrid.class).single(); @@ -44,7 +44,7 @@ public void cleanUp() { @Test public void error() { - assertEquals("Product id \"1234\" not valid or found.", + assertEquals("Product id \"12345\" not valid or found.", $(Notification.class).last().getCaption()); assertFalse(form.isShown()); } diff --git a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewNewTest.java b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewNewTest.java index 70b165e..9ff14de 100644 --- a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewNewTest.java +++ b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewNewTest.java @@ -30,7 +30,6 @@ public class BooksViewNewTest extends AbstractUITest { private BooksView view; private BookGrid grid; private BookForm form; - private Collection backup; @Before public void setup() throws ServiceException { @@ -38,20 +37,16 @@ public void setup() throws ServiceException { mockVaadin(ui); login(); - backup = ui.getProductService().backup(); - view = navigate(BooksView.VIEW_NAME + "/new", BooksView.class); var layout = $(view, VerticalLayout.class).first(); grid = $(layout, BookGrid.class).single(); waitForGrid(layout, grid); form = $(view, BookForm.class).single(); - } @After public void cleanUp() { - ui.getProductService().restore(backup); logout(); tearDown(); } @@ -86,6 +81,8 @@ public void newProduct() { assertEquals("A new product", test(grid).cell(1, row)); assertEquals("10.00 €", test(grid).cell(2, row)); assertEquals("10", test(grid).cell(4, row)); + + ui.getProductService().deleteProduct(test(grid).item(row).getId()); } } diff --git a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewTest.java b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewTest.java index 450e747..00d12d7 100644 --- a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewTest.java +++ b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/crud/BooksViewTest.java @@ -2,7 +2,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -12,8 +11,8 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.util.Collection; import java.util.Collections; +import java.util.stream.Collectors; import org.junit.After; import org.junit.Before; @@ -47,7 +46,6 @@ public class BooksViewTest extends AbstractUITest { private BooksView view; private BookGrid grid; private BookForm form; - private Collection backup; @Before public void setup() throws ServiceException { @@ -55,8 +53,6 @@ public void setup() throws ServiceException { mockVaadin(ui); login(); - backup = ui.getProductService().backup(); - view = navigate(BooksView.VIEW_NAME, BooksView.class); var layout = $(view, VerticalLayout.class).first(); @@ -67,7 +63,6 @@ public void setup() throws ServiceException { @After public void cleanUp() { - ui.getProductService().restore(backup); logout(); tearDown(); } @@ -191,7 +186,8 @@ public void addProduct() { assertTrue( test($(form, TextField.class).id("product-name")).isFocused()); - test($(form, TextField.class).id("product-name")).setValue("New book"); + test($(form, TextField.class).id("product-name")) + .setValue("Filter book"); test($(form, TextField.class).id("price")).setValue("10.0 €"); test($(form, AvailabilitySelector.class).id("availability")) .clickItem(Availability.AVAILABLE); @@ -206,24 +202,27 @@ public void addProduct() { assertTrue(test(grid).isFocused()); - assertTrue( - $(Notification.class).last().getCaption().contains("New book")); + assertTrue($(Notification.class).last().getCaption() + .contains("Filter book")); assertTrue(ui.getProductService().getAllProducts().stream() - .anyMatch(b -> b.getProductName().equals("New book"))); + .anyMatch(b -> b.getProductName().equals("Filter book"))); // New book is added to the end int row = test(grid).size() - 1; - assertEquals("New book", test(grid).cell(1, row)); + assertEquals("Filter book", test(grid).cell(1, row)); assertEquals("10.00 €", test(grid).cell(2, row)); assertEquals("10", test(grid).cell(4, row)); // Find by filter and its the first row - test($(TextField.class).id("filter-field")).setValue("New book"); + test($(TextField.class).id("filter-field")).setValue("Filter book"); assertEquals(1, test(grid).size()); - assertEquals("New book", test(grid).cell(1, 0)); + assertEquals("Filter book", test(grid).cell(1, 0)); assertEquals("10.00 €", test(grid).cell(2, 0)); assertEquals("10", test(grid).cell(4, 0)); + + // Cleanup + ui.getProductService().deleteProduct(test(grid).item(0).getId()); } @Test @@ -258,11 +257,15 @@ public void addAndCancelEmpty() { @Test public void deleteProduct() { - var book = test(grid).item(0); - test(grid).click(1, 0); + createBook("Delete book"); + test($(TextField.class).id("filter-field")).setValue("Delete book"); + + var row = 0; + var book = test(grid).item(row); var id = book.getId(); - var name = book.getProductName(); + assertEquals("Delete book", book.getProductName()); + test(grid).click(1, row); test($(form, Button.class).id("delete-button")).click(); @@ -275,15 +278,34 @@ public void deleteProduct() { assertEquals(null, ui.getProductService().getProductById(id)); - var newName = test(grid).cell(1, 0); - assertNotEquals(name, newName); + assertEquals(0, test(grid).size()); + } + + @SuppressWarnings("unchecked") + private void createBook(String name) { + test($(view, Button.class).id("new-product")).click(); + test($(TextField.class).id("product-name")).setValue(name); + test($(AvailabilitySelector.class).id("availability")) + .clickItem(Availability.AVAILABLE); + test($(TextField.class).id("price")).setValue("35.0 €"); + var categories = VaadinCreateUI.get().getProductService() + .getAllCategories().stream().collect(Collectors.toList()); + test($(CheckBoxGroup.class).id("category")) + .clickItem(categories.get(1)); + test($(CheckBoxGroup.class).id("category")) + .clickItem(categories.get(2)); + test($(NumberField.class).id("stock-count")).setValue(100); + test($(Button.class).id("save-button")).click(); } @Test public void concurrentDelete() { + createBook("Concurrent delete"); + // Simulate other user deleting the book + test($(TextField.class).id("filter-field")) + .setValue("Concurrent delete"); var book = test(grid).item(0); - var name = book.getProductName(); ui.getProductService().deleteProduct(book.getId()); test(grid).click(1, 0); @@ -291,17 +313,22 @@ public void concurrentDelete() { $(Notification.class).last().getCaption()); assertFalse(form.isShown()); - var newName = test(grid).cell(1, 0); - assertNotEquals(name, newName); + assertEquals(0, test(grid).size()); } @Test public void editProduct() { - var book = test(grid).item(0); - test(grid).click(1, 0); + createBook("Test book"); + + test($(TextField.class).id("filter-field")).setValue("Test book"); + + var row = 0; + var book = test(grid).item(row); + test(grid).click(1, row); assertTrue(form.isShown()); var id = book.getId(); + assertNotNull(id); test($(form, TextField.class).id("product-name")) .setValue("Edited book"); @@ -310,13 +337,15 @@ public void editProduct() { var edited = ui.getProductService().getProductById(id); - var name = (String) test(grid).cell(1, 0); + test($(TextField.class).id("filter-field")).setValue("Edited book"); + var name = (String) test(grid).cell(1, row); assertEquals("Edited book", name); assertEquals("Edited book", edited.getProductName()); assertEquals(VaadinCreateTheme.BOOKVIEW_GRID_EDITED, test(grid).styleName(0)); + ui.getProductService().deleteProduct(id); } @Test @@ -415,8 +444,9 @@ public void openProductChangedByOtherUser() { // Simulate other user persisting a change to the database var edited = ui.getProductService().getProductById(book.getId()); + var productName = edited.getProductName(); edited.setProductName("Touched book"); - ui.getProductService().updateProduct(edited); + var saved = ui.getProductService().updateProduct(edited); test(grid).click(1, 0); assertTrue(form.isShown()); @@ -430,6 +460,9 @@ public void openProductChangedByOtherUser() { test($(form, Button.class).id("cancel-button")).click(); assertFalse(form.isShown()); + + edited.setProductName(productName); + ui.getProductService().updateProduct(saved); } @Test @@ -445,8 +478,10 @@ public void editLockedProduct() { @Test public void weakLockConcurrentEdit() { - // Idea of this is to simulate Form being detached due browser crash so - // that form no longer holds the product reference. Here it is simulated + // Idea of this is to simulate Form being detached due browser + // crash so + // that form no longer holds the product reference. Here it is + // simulated // by doing lock via proxy object. var book = new Product(test(grid).item(0)); LockedObjects.get().lock(book, CurrentUser.get().get()); @@ -460,7 +495,7 @@ public void weakLockConcurrentEdit() { assertTrue(form.isShown()); // Concurrent edit is now possible, simulate it - ui.getProductService().updateProduct(test(grid).item(0)); + ui.getProductService().updateProduct(new Product(test(grid).item(0))); // Change and save test($(form, TextField.class).id("product-name")) @@ -476,9 +511,15 @@ public void weakLockConcurrentEdit() { @Test public void weakLockConcurrentDelete() { - // Idea of this is to simulate Form being detached due browser crash so - // that form no longer holds the product reference. Here it is simulated + // Idea of this is to simulate Form being detached due browser + // crash so + // that form no longer holds the product reference. Here it is + // simulated // by doing lock via proxy object. + createBook("Concurrent delete"); + test($(TextField.class).id("filter-field")) + .setValue("Concurrent delete"); + var book = new Product(test(grid).item(0)); LockedObjects.get().lock(book, CurrentUser.get().get()); assertEquals("Edited by Admin", test(grid).description(0)); @@ -579,6 +620,9 @@ public void editProductRevertEdit() { // Assert that form content was updated assertEquals($(form, TextField.class).id("product-name").getValue(), test(grid).cell(1, 1)); + + test($(form, Button.class).id("cancel-button")).click(); + assertFalse(form.isShown()); } @Test @@ -594,6 +638,8 @@ public void editProductDiscardChangesWhenNavigate() { var dialog = $(Window.class).id("confirm-dialog"); test($(dialog, Button.class).id("confirm-button")).click(); + assertFalse(form.isShown()); + assertEquals(1, $(AboutView.class).size()); assertEquals(0, $(BooksView.class).size()); } @@ -644,10 +690,12 @@ public void categoriesValid() { var category = new Category(); category.setName("Science"); category = ui.getProductService().updateCategory(category); + assertNotNull(category.getId()); test(grid).click(1, 0); - // Simulate other user deleting the category while editor is open + // Simulate other user deleting the category while editor is + // open ui.getProductService().deleteCategory(category.getId()); test($(form, CheckBoxGroup.class).id("category")).clickItem(category); @@ -655,6 +703,9 @@ public void categoriesValid() { assertEquals("One or more of the selected categories were deleted.", $(Notification.class).last().getCaption()); + + test($(form, Button.class).id("cancel-button")).click(); + assertFalse(form.isShown()); } @Test @@ -685,7 +736,10 @@ public void sanitation() { // Assert text content remain assertTrue(test(grid).description(row).contains("A new book")); - test(grid).click(1,row); + test(grid).click(1, row); + var id = test(grid).item(row).getId(); + assertNotNull(id); + assertTrue(form.isShown()); test($(form, TextField.class).id("product-name")) @@ -707,6 +761,8 @@ public void sanitation() { test($(form, Button.class).id("discard-button")).click(); test($(form, Button.class).id("cancel-button")).click(); assertFalse(form.isShown()); + + ui.getProductService().deleteProduct(id); } @Test diff --git a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/locking/LockedObjectsTest.java b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/locking/LockedObjectsTest.java index 0321b66..385beb4 100644 --- a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/locking/LockedObjectsTest.java +++ b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/locking/LockedObjectsTest.java @@ -41,7 +41,7 @@ public void lockAndUnlockObject() { var event = listener.getLastEvent(); assertEquals(1, listener.getEventCount()); assertEquals(user, event.getUser()); - assertEquals(objects.get(0).getId(), (int) event.getId()); + assertEquals(objects.get(0).getId(), event.getId()); assertEquals(MockObject.class, event.getType()); assertTrue(event.isLocked()); @@ -51,7 +51,7 @@ public void lockAndUnlockObject() { event = listener.getLastEvent(); assertEquals(2, listener.getEventCount()); assertEquals(user, event.getUser()); - assertEquals(objects.get(0).getId(), (int) event.getId()); + assertEquals(objects.get(0).getId(), event.getId()); assertEquals(MockObject.class, event.getType()); assertFalse(event.isLocked()); diff --git a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/stats/StatsViewTest.java b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/stats/StatsViewTest.java index 1c52fe0..eb799bf 100644 --- a/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/stats/StatsViewTest.java +++ b/vaadincreate-ui/src/test/java/org/vaadin/tatu/vaadincreate/stats/StatsViewTest.java @@ -127,7 +127,6 @@ private void assertStatistics(Map prices, private static Product createBook() { var book = new Product(); - book.setId(-1); book.setProductName("Test"); book.setAvailability(Availability.AVAILABLE); var categories = VaadinCreateUI.get().getProductService()