Доступ к атрибутам пользовательской сессии из BackgroundTask

Добрый день. Версия Jmix 1.5.3. Столкнулись с проблемой доступа к атрибутам пользовательской сессии при вызове компонентов из BackgroundTask. При обращении они просто выбрасывают ошибку. Если вызывать через UI поток то все работает.

Содержание ошибки:

org.springframework.beans.factory.support.ScopeNotActiveException: Error creating bean with name 'core_SessionData': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:383) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1391) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory$DependencyObjectProvider.getObject(DefaultListableBeanFactory.java:1990) ~[spring-beans-5.3.28.jar:5.3.28]
         ...
	at io.jmix.ui.executor.LocalizedTaskWrapper.run(LocalizedTaskWrapper.java:56) ~[jmix-ui-1.5.3.jar:na]
	at io.jmix.ui.executor.impl.WebBackgroundWorker$WebTaskExecutor.call(WebBackgroundWorker.java:206) ~[jmix-ui-1.5.3.jar:na]
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
	at io.jmix.ui.executor.impl.WebBackgroundWorker$WebTaskExecutor.lambda$startExecution$1(WebBackgroundWorker.java:385) ~[jmix-ui-1.5.3.jar:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
	at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na]
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
	at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-5.3.28.jar:5.3.28]
	at org.springframework.web.context.request.SessionScope.get(SessionScope.java:55) ~[spring-web-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:371) ~[spring-beans-5.3.28.jar:5.3.28]
	... 14 common frames omitted

Воспроизводится на чистой версии.

Пример экрана, при нажатия на кнопку

    @Autowired
    private Dialogs dialogs;
    @Autowired
    private Notifications notifications;
    @Autowired
    private ClientChecker clientChecker;

    @Subscribe("btn")
    public void onClick(Button.ClickEvent event) {
        BackgroundTask<Integer, Boolean> task = new BackgroundTask<>(1, TimeUnit.MINUTES, this) {
            @Override
            public Boolean run(TaskLifeCycle<Integer> cycle) {
                return clientChecker.check();
            }

            @Override
            public void done(Boolean res) {
                notifications.create(Notifications.NotificationType.HUMANIZED)
                        .withCaption(res ? "Accepted" : "Failed")
                        .show();
            }
        };
        dialogs.createBackgroundWorkDialog(this, task)
                .withCancelAllowed(false)
                .build()
                .show();
    }

Пример компонента где происходит чтение/запись атрибутов

@Component
public class ClientChecker {

    @Autowired
    private ObjectProvider<SessionData> sessionDataProvider;

    public boolean check() {
        sessionDataProvider.getObject().setAttribute("clientCode", "123");
        return true;
    }
}

Как понял там немного другая тема. Здесь же иногда невозможно знать как данные будут использоваться внутри вызываемых компонентов (они могут транзитивно вызыват другие части программы, например слушателей). Так же не будут работать допустим Row-level роли.

В пример в каком-то компоненте есть код:

dataManager.load(User.class)
                .query("select e from bg_User e")
                .fetchPlan(FetchPlan.INSTANCE_NAME)
                .one()

Тогда не применится следующее ограничение на чтение User: {E}.username = :session_clientCode. В логах будет варнинг:

i.j.d.i.SessionQueryParamValueProvider   : Unable to get session attribute session_clientCode: org.springframework.beans.factory.support.ScopeNotActiveException: Error creating bean with name 'core_SessionData

Это немного неочевидно.

Добрый день. Проблема известная: issue. Дело в том, что текущая реализация атрибутов сессии хранит значения в http-сессии. Background таски создают отдельные потоки, которые не имеют к ней доступа.

Спасибо Максим за пояснение. Покопался чуть-чуть в реализации, сделано небольшое обходное решение для проекта, вдруг кому-то будет полезно (а может в платформе на основе этого будет решение в будущем).

Сделан синглтон бин для доступа к атрибутам. Он пробует получить http-сессию сначало из ваадина, потом из sessiondata.

@Component
public class UserSessionBean {

    @Autowired
    private ObjectProvider<SessionData> sessionDataObjectProvider;

    public <T> T getAttribute(String name) {
        WrappedSession session = getWrappedSession();
        if (session != null) {
            return (T) session.getAttribute(name);
        } else {
            return (T) sessionDataObjectProvider.getObject().getAttribute(name);
        }
    }

    public <T> void setAttribute(String name, T value) {
        WrappedSession session = getWrappedSession();
        if (session != null) {
            session.setAttribute(name, value);
        } else {
            sessionDataObjectProvider.getObject().setAttribute(name, value);
        }
    }

    private WrappedSession getWrappedSession() {
        VaadinSession session = VaadinSession.getCurrent();
        return session == null ? null : session.getSession();
    }
}

Используем этот компонент везде где нужен доступ к атрибутам.

Когда запускаем задачу передаем статиком vaadin сессию в новый поток:

VaadinSession vaadinSession = VaadinSession.getCurrent();
BackgroundTask<Integer, Boolean> task = new BackgroundTask<>(1, TimeUnit.MINUTES, this) {
   @Override
   public Boolean run(TaskLifeCycle<Integer> cycle) {
          VaadinSession.setCurrent(vaadinSession);
           try {
               //доступ к атрибутам есть
           } finally {
                VaadinSession.setCurrent(null);
           }
   }
}
1 симпатия