Buffered table with in-memory datasource

Hey all,
I’ve got a bit of an issue I wanted to ask you about, as I haven’t been able to figure out a good way to overcome it.

I have a multi user application which uses a Postgres database to store permanant data. I want to be able to cache some of that frequently used data inside the application.

I’ll give a quick example of my issue: I have a singleton which holds a hashmap, and provides all access to that map. It allows you to add, remove, overwrite, check if exists, return a value, etc… I load that singleton on startup of my application from the database.
Say the data in that singleton is account data, and I have an account management page which has an editable table. I want to be able to add the data from that singleton to some datasource, and have it not save changes back to the original items until a commit is called.

My issue is, I load the BeanContainer from my singleton, and any changes that are made to any field in the table are instantly reflected in that singleton without even calling a commit. I expected the table to buffer the changes to the datasource until a commit was called, but that is not the case (I have setBuffered(true) set on the table).

How can I get around this?

I don’t even… gah.

I have messed with this for days, gone through a bunch of things such as testing deep copy to mitigate (way too slow), and had no results.

I post this, then I think “what if I set each field to buffered in the field factory?!”…
It worked. I feel dumb for wasting as much time as I have.


Edit: Never mind, didn’t work, because now nothing will commit to the container datasource.

public class AccountAdminView extends Page {
    /**
     *
     */
    private static final long serialVersionUID = -2215388422551586484L;
    private final FormLayout form = new FormLayout();
    private final HorizontalLayout buttons = new HorizontalLayout();
    private CustomBeanContainer<Account, Account> container = new CustomBeanContainer(Account.class, AccountManager.INSTANCE, AccountContainer.INSTANCE);
    private final FilterTable table = new FilterTable();
    private final Label prompt = new Label();
    private final Button add = new Button();
    private final Button save = new Button();
    private final Button resetPass = new Button();
    private String textFieldWidth = "300px";

    public AccountAdminView() {
        setProperties();
        layoutPage();
    }

    private boolean validate() {
        try {
            table.validate();
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    @Override
    public void setProperties() {
        // TODO Auto-generated method stub

        form.setSizeUndefined();
        form.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER);

        // Prompt
        prompt.setValue("Please enter your new account information");

        container.setBeanIdResolver(bean -> bean);

        CustomFieldFactory fieldFactory = new CustomFieldFactory() {
            @Override
            public Field createField(final Container container, final Object itemId, final Object propertyId, Component uiContext) {
                Field<?> field = super.createField(container, itemId, propertyId, uiContext);

                if (propertyId.equals("ID")) {
                    field.setReadOnly(true);
                    ((TextField) field).setConverter(new StringToPlainIntegerConverter());
                }
                field.setBuffered(true);
                field.addValidator(new BeanValidator(Account.class, (String) propertyId));
                return field;
            }
        };

        // table
        table.setWidth("100%");
        table.setContainerDataSource(container);
        table.setImmediate(true);
        table.setEditable(true);
        table.setBuffered(true);
        table.setSelectable(true);
        table.setPageLength(10);
        table.setFilterBarVisible(true);
        table.setFilterGenerator(new MyFilterGenerator());
        table.setFilterDecorator(new MyFilterDecorator());
        table.setTableFieldFactory(fieldFactory);
        table.setVisibleColumns("ID", "email", "firstName", "lastName", "active");
        table.setColumnWidth("ID", 80);
        table.setColumnWidth("email", 250);


        // Layout for buttons
        buttons.setWidth("200px");
        buttons.setSpacing(false);

        // add
        add.setCaption("Add");
        add.addClickListener(event -> {
            if(!table.getItemIds().contains("Temporary row id")) {
                Account newAccount = new Account();
                newAccount.setClientID(AccountContainer.INSTANCE.get(getSession()).getClientID());
                String newPass = RandomStringGenerator.generateRandomString(6, RandomStringGenerator.Mode.ALPHA);
                newAccount.setPassword(newPass);
                newAccount.setActive(true);
                container.addBean(newAccount);
                Notification.show("The password for the new account you created is: " + newPass, Type.ERROR_MESSAGE);
                table.refreshRowCache();
            }
            else {
                Notification.show("Only one account can be added at a time.  Please save the existing account before adding another.", Type.WARNING_MESSAGE);
            }
        });
        add.setImmediate(true);
        add.setWidth("65px");
        add.setHeight("25px");

        // save
        save.setCaption("Save");
        save.addClickListener(event -> {
            try {
                validate();
                table.commit();
                container.commit();
                Notification.show("Account saved.");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                Notification.show("Account could not be created / saved.", Type.ERROR_MESSAGE);

                //remove uncommitted rows from the table

                List<Object> remove = table.getItemIds().stream().filter(item -> ((Account) item).getID() == null).collect(Collectors.toList());
                for(Object i : remove) {
                    table.removeItem(i);
                }
            }
        });
        save.setImmediate(true);
        save.setWidth("65px");
        save.setHeight("25px");

        // resetPass
        resetPass.setCaption("Reset Password");
        resetPass.addClickListener(event -> {
            if (table.getValue() == null) {
                Notification.show("You must select an account first.", Type.WARNING_MESSAGE);
                return;
            }
            Account account = (Account) AccountContainer.INSTANCE.get((Integer) table.getValue());
            String newPass = RandomStringGenerator.generateRandomString(6, RandomStringGenerator.Mode.ALPHA);
            if (AccountManager.INSTANCE.updatePassword(account, newPass)) {
                Notification.show("Password successfully set to: " + newPass, Type.ERROR_MESSAGE);
            } else
                Notification.show("The password could not be reset for this account", Type.ERROR_MESSAGE);
        });
        resetPass.setImmediate(true);
        resetPass.setWidth("120px");
        resetPass.setHeight("25px");
    }


    @Override
    public void layoutPage() {
        // TODO Auto-generated method stub
        components.addComponent(prompt);
        components.addComponent(resetPass);
        components.setComponentAlignment(resetPass, Alignment.MIDDLE_LEFT);
        components.addComponent(table);
        buttons.addComponent(add);
        buttons.addComponent(save);
        buttons.setComponentAlignment(add, Alignment.MIDDLE_LEFT);
        buttons.setComponentAlignment(save, Alignment.MIDDLE_RIGHT);
        components.addComponent(buttons);
    }

    @Override
    public void enter(ViewChangeListener.ViewChangeEvent event) {
        super.enter(event);
        if (AccountContainer.INSTANCE.get(getSession()) == null)
            return;

        container.removeAllItems();
        container.addAll(AccountContainer.INSTANCE.getAll(AccountContainer.INSTANCE.get(getSession()).getClientID()));
    }
}

Here is the code for my page i’m trying to get working properly. Probably best to just show what is going on.