Skip to content

Commit

Permalink
Pessimistic locking example
Browse files Browse the repository at this point in the history
  • Loading branch information
TatuJLund committed May 26, 2024
1 parent 3705f48 commit 689fc28
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class VaadinCreateTheme {

public static final String BOOKVIEW_GRID_ALIGNRIGHT = "bookview-grid-alignright";
public static final String BOOKVIEW_GRID_EDITED = "bookview-grid-edited";
public static final String BOOKVIEW_GRID_LOCKED = "bookview-grid-locked";
public static final String BOOKVIEW_AVAILABILITYLABEL = "bookview-availabilitylabel";

public static final String BOOKFORM_FORM = "bookform-form";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@
import java.util.Comparator;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vaadin.tatu.vaadincreate.VaadinCreateTheme;
import org.vaadin.tatu.vaadincreate.backend.data.Availability;
import org.vaadin.tatu.vaadincreate.backend.data.Category;
import org.vaadin.tatu.vaadincreate.backend.data.Product;
import org.vaadin.tatu.vaadincreate.crud.LockedBooks.BookEvent;
import org.vaadin.tatu.vaadincreate.eventbus.EventBus;
import org.vaadin.tatu.vaadincreate.eventbus.EventBus.EventBusListener;
import org.vaadin.tatu.vaadincreate.i18n.HasI18N;
import org.vaadin.tatu.vaadincreate.util.Utils;

import com.vaadin.data.ValueContext;
import com.vaadin.data.provider.ListDataProvider;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.ContentMode;
Expand All @@ -28,7 +34,8 @@
* data sets.
*/
@SuppressWarnings("serial")
public class BookGrid extends Grid<Product> implements HasI18N {
public class BookGrid extends Grid<Product>
implements HasI18N, EventBusListener {

private static final String CATEGORIES = "categories";
private static final String IN_STOCK = "in-stock";
Expand All @@ -39,6 +46,8 @@ public class BookGrid extends Grid<Product> implements HasI18N {

private Registration resizeReg;
private Label availabilityCaption;
private LockedBooks lockedBooks = LockedBooks.get();
private EventBus eventBus = EventBus.get();

private Product editedProduct;
private int edited;
Expand All @@ -49,9 +58,15 @@ public BookGrid() {
setSizeFull();

// Set highlight color to last edited row with style generator.
setStyleGenerator(book -> book.getId() == edited
? VaadinCreateTheme.BOOKVIEW_GRID_EDITED
: "");
setStyleGenerator(book -> {
if (book.getId() == edited) {
return VaadinCreateTheme.BOOKVIEW_GRID_EDITED;
}
if (lockedBooks.lockedBooks().contains(book.getId())) {
return VaadinCreateTheme.BOOKVIEW_GRID_LOCKED;
}
return "";
});

addColumn(Product::getId, new NumberRenderer()).setCaption("Id")
.setResizable(false);
Expand Down Expand Up @@ -95,6 +110,8 @@ public BookGrid() {
// Show all categories the product is in, separated by commas
addColumn(this::formatCategories).setCaption(getTranslation(CATEGORIES))
.setResizable(false).setSortable(false);

eventBus.registerEventBusListener(this);
}

public Product getSelectedRow() {
Expand Down Expand Up @@ -231,6 +248,7 @@ public void detach() {
// It is necessary to remove resize listener upon detach to avoid
// resource leakage.
resizeReg.remove();
eventBus.unregisterEventBusListener(this);
super.detach();
}

Expand All @@ -245,4 +263,22 @@ public void setEdited(Product product) {
edited = product != null ? product.getId() : -1;
editedProduct = product;
}

@SuppressWarnings("unchecked")
@Override
public void eventFired(Object event) {
if (event instanceof BookEvent && isAttached()) {
logger.info("Book locking update");
var bookEvent = (BookEvent) event;
getUI().access(() -> {
ListDataProvider<Product> dataProvider = (ListDataProvider<Product>) getDataProvider();
dataProvider.getItems().stream()
.filter(book -> book.getId() == bookEvent.getId())
.findFirst().ifPresent(
product -> dataProvider.refreshItem(product));
});
}
}

private Logger logger = LoggerFactory.getLogger(this.getClass());
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.slf4j.LoggerFactory;
import org.vaadin.tatu.vaadincreate.VaadinCreateUI;
import org.vaadin.tatu.vaadincreate.auth.AccessControl;
import org.vaadin.tatu.vaadincreate.auth.CurrentUser;
import org.vaadin.tatu.vaadincreate.backend.ProductDataService;
import org.vaadin.tatu.vaadincreate.backend.data.Category;
import org.vaadin.tatu.vaadincreate.backend.data.Product;
Expand All @@ -34,6 +35,8 @@ public class BooksPresenter implements Serializable {
.getProductService();
private AccessControl accessControl = VaadinCreateUI.get()
.getAccessControl();
private LockedBooks lockedBooks = LockedBooks.get();
private Integer editing;

public BooksPresenter(BooksView simpleCrudView) {
view = simpleCrudView;
Expand All @@ -59,6 +62,7 @@ public void requestUpdateProducts() {
}

public void cancelUpdateProducts() {
unlockBook();
if (future != null) {
boolean cancelled = future.cancel(true);
future = null;
Expand All @@ -76,6 +80,22 @@ public void init() {

public void cancelProduct() {
view.cancelProduct();
unlockBook();
}

private void unlockBook() {
if (editing != null) {
lockedBooks.unlock(editing);
editing = null;
}
}

private void lockBook(Integer id) {
if (editing != null) {
unlockBook();
}
lockedBooks.lock(id);
editing = id;
}

public void enter(String productId) {
Expand Down Expand Up @@ -114,6 +134,7 @@ public void saveProduct(Product product) {
view.updateGrid(savedProduct);
} else {
view.updateProduct(savedProduct);
unlockBook();
}
view.setFragmentParameter("");
}
Expand All @@ -124,14 +145,17 @@ public void deleteProduct(Product product) {
logger.info("Deleting product: {}", product.getId());
service.deleteProduct(product.getId());
view.removeProduct(product);
unlockBook();
view.setFragmentParameter("");
}

public void editProduct(Product product) {
if (product == null) {
view.setFragmentParameter("");
unlockBook();
} else {
view.setFragmentParameter(product.getId() + "");
lockBook(product.getId());
}
logger.info("Editing product: {}",
product != null ? product.getId() : "none");
Expand All @@ -147,7 +171,12 @@ public void newProduct() {

public void rowSelected(Product product) {
if (accessControl.isUserInRole(Role.ADMIN)) {
editProduct(product);
if (product != null
&& lockedBooks.lockedBooks().contains(product.getId())) {
view.clearSelection();
} else {
editProduct(product);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.vaadin.tatu.vaadincreate.crud;

import java.io.Serializable;
import java.util.Collection;

@SuppressWarnings("serial")
public interface LockedBooks {

public Collection<Integer> lockedBooks();

public void lock(Integer id);

public void unlock(Integer id);

public static LockedBooks get() {
return LockedBooksImpl.getInstance();
}

public static class BookEvent implements Serializable {
private Integer id;

public BookEvent(Integer id) {
this.id = id;
}

public Integer getId() {
return id;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.vaadin.tatu.vaadincreate.crud;

import java.util.Collection;
import java.util.WeakHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vaadin.tatu.vaadincreate.eventbus.EventBus;

@SuppressWarnings("serial")
public class LockedBooksImpl implements LockedBooks {

private static LockedBooksImpl INSTANCE;
private EventBus eventBus = EventBus.get();

private final WeakHashMap<Integer, Object> books = new WeakHashMap<>();

public synchronized static LockedBooks getInstance() {
if (INSTANCE == null) {
INSTANCE = new LockedBooksImpl();
}
return INSTANCE;
}

private LockedBooksImpl() {
}

@Override
public Collection<Integer> lockedBooks() {
synchronized (books) {
return books.keySet();
}
}

@Override
public void lock(Integer id) {
synchronized (books) {
var match = books.keySet().stream().filter(i -> i.equals(id))
.findFirst();
if (match.isPresent()) {
throw new IllegalStateException(
"Can't open book already opened: " + id);
}
books.put(id, null);
eventBus.post(new BookEvent(id));
logger.info("Locked book {}", id);
}
}

@Override
public void unlock(Integer id) {
synchronized (books) {
var match = books.keySet().stream().filter(i -> i.equals(id))
.findFirst();
if (match.isPresent()) {
books.remove(match.get());
}
eventBus.post(new BookEvent(id));
logger.info("Unlocked book {}", id);
}
}

private Logger logger = LoggerFactory.getLogger(this.getClass());
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ $fake-grid-background-color: white;

@import "../valo/valo.scss";

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

@mixin vaadincreate {
@include valo;
Expand Down Expand Up @@ -435,9 +436,15 @@ $fake-grid-background-color: white;
.bookview-grid-edited {
color: $v-focus-color;
}

.bookview-grid-locked:not(.v-grid-row-selected) {
opacity: $v-disabled-opacity;
pointer-events: none;
animation: fadeIn 1s ease-in-out 0s;
}

.bookview-grid-descriptioncaption {
color: $highlight-contrast-color;
color: $highlight-contrast-color;
}

.whitespace-pre {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ protected void waitForGrid(VerticalLayout layout, BookGrid grid) {

protected void waitForCharts(VerticalLayout layout, CssLayout dashboard) {
assertFalse(dashboard.getStyleName().contains("loaded"));
waitWhile(dashboard, d -> !d.getStyleName().contains("loaded"), 10);
waitWhile(dashboard, d -> !d.getStyleName().contains("loaded"), 15);
}

}

0 comments on commit 689fc28

Please sign in to comment.