View contains data of other users when accessed simultaneously

Hi,

I’ve just noticed that it seems that data loaded by a user into a view, appears in the same view of another user.

In other words, here is what happens:
User 1 & User 2 visit the same MyView at approximatively the same time. MyView extends VerticalLayout, implements AfterNavigationObserver and contains a Grid that is instanciated and added by a method tagged @PostConstruct. The grid is filled by data retrieved by a service that is called in the overriden afterNavigation method of the same class. Once the view is loaded, User 1 has only his data in the grid, whereas User 2 has both his data and User 1’s.

I’ve verified and the instances are different (different object hashes) and access by a different user.

Is this the expected behaviour???

Here is an excerpt of the MyView class:

package com.ixm.sna.view;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.ixm.sna.entity.ProcedureTest;
import com.ixm.sna.entity.RefFirewallRuleType;
import com.ixm.sna.entity.repository.SysparamRepository;
import com.ixm.sna.restconsumer.ProcedureTestsRestConsumer;
import com.ixm.sna.service.UserService;
import com.ixm.sna.thread.ProcedureTestThread;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.H5;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.AfterNavigationEvent;
import com.vaadin.flow.router.AfterNavigationObserver;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.UIScope;
import org.apache.commons.lang3.ObjectUtils;

@CssImport(value = "frontend://css/grid-cell.css", themeFor = "vaadin-grid")
@Route(value = "MyView", layout = LayoutWithMenuBar.class)
@UIScope
@Component
public class MyView extends VerticalLayout implements AfterNavigationObserver {
    private final Logger logger = LoggerFactory.getLogger(MyView.class);

	@Autowired
    private ProcedureTestsRestConsumer restConsumer;

    @Autowired
    private SysparamRepository sysparamRepository;

    @Autowired
    private UserService userService;

    private Grid<ProcedureTest> myGrid;

    @PostConstruct
    public void init() {
    	setUpView();
    }

    private void setUpView() {
        H5 header = new H5("My View");
        add(header);

        myGrid = new Grid<>(ProcedureTest.class);
        myGrid.setHeightByRows(true);

        myGrid.setColumns(
            "name", "description", "status"
        );

        myGrid.addColumn(new ComponentRenderer<>(ct -> {
            if (!(ct instanceof ProcedureTest)) {
                return new Paragraph();
            }

            ProcedureTest procedureTest = (ProcedureTest)ct;

            if (procedureTest.getProcedureTestResult() == ProcedureTest.ProcedureTestResult.NOT_REACHABLE) {
                return new Icon(VaadinIcon.CLOSE_CIRCLE);
            } else if (procedureTest.getProcedureTestResult() == ProcedureTest.ProcedureTestResult.REACHABLE) {
                return new Icon(VaadinIcon.CHECK);
            } else {
                // no icon if not tested
                return new Paragraph();
            }
        })).setHeader("Test status").setWidth("50px");

        myGrid.setClassNameGenerator(ct -> {
            if (!(ct instanceof ProcedureTest)) {
                return "error";
            }

            ProcedureTest procedureTest = (ProcedureTest)ct;
            
            if (procedureTest.getProcedureTestResult()
                .equals(ProcedureTest.ProcedureTestResult.REACHABLE)) {
                return "success";
            } else if (procedureTest.getProcedureTestResult()
                .equals(ProcedureTest.ProcedureTestResult.NOT_TESTED)) {
                return "not_tested";
            } else {
                return "error";
            }
        });

        final Button buttonStartTests  = new Button("Start tests");
        HorizontalLayout firewallButtonStartTest = new HorizontalLayout(buttonStartTests);

        add(myGrid, firewallButtonStartTest);

        buttonStartTests.addClickListener((event) -> {
            buttonStartTests.setEnabled(false);
            List<ProcedureTest> procedureTests = myGrid.getDataProvider().fetch(new Query<>()).collect(Collectors.toList());
            procedureTests.stream().forEach(ct -> ct.setProcedureTestResult(ProcedureTest.ProcedureTestResult.NOT_TESTED));

            new ProcedureTestThread(sysparamRepository.getProcedureTestTimeout(), procedureTests, UI.getCurrent(), (ct, lastFinished) -> {
                if (lastFinished) {
                    buttonStartTests.setEnabled(true);
                    myGrid.getDataProvider().refreshAll();
                } else {
                    myGrid.getDataProvider().refreshItem(ct);
                }
            });
        });

    }

    @Override
    public void afterNavigation(AfterNavigationEvent ane) {
        logger.info("Object accessed: "+ ObjectUtils.identityToString(this) +"; by: "+ userService.getCurrentUser().getUsername());

        try {
            logger.info("Getting procedureTests.");
            List<ProcedureTest> procedureTests = restConsumer.getProcedureTests();
            myGrid.setItems(procedureTests);
        } catch (Exception e) {
            logger.error("Error getting procedureTests.", e);
            myGrid.setItems(new ArrayList<>());
        }
    }

}

Thanks for your help!

ixM

Based on a quick glance, I’d assume the issue is within your restConsumer.

You’re perfectly right… Yesterday evening, I finally noticed that, stupidly, I had stored a list in there as a member of the RestConsumer and though Vaadin Views are different objects for each user/session (make sense) the Spring boot services are instanciated just one time for the whole application (they’re stateless).

Thanks for your help!

Edit> Btw, Vaadin is really great!