From a81a19a9a3e45a89ed3b7c783bd5747cc27f6aa1 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sun, 24 Jul 2022 13:21:49 +0200 Subject: [PATCH] Improve UI event support --- .../src/org/argeo/api/cms/CmsApp.java | 9 +++- .../src/org/argeo/api/cms/ux/CmsView.java | 23 ++++------- .../src/org/argeo/cms/LocaleUtils.java | 5 ++- .../cms/internal/runtime/CmsEventBusImpl.java | 3 +- .../org/argeo/cms/swt/AbstractSwtCmsView.java | 41 ++++++++++++++++--- .../cms/swt/dialogs/ChangePasswordDialog.java | 4 +- .../argeo/cms/e4/rap/CmsLoginLifecycle.java | 6 +++ .../org/argeo/cms/web/CmsWebEntryPoint.java | 15 ++++++- 8 files changed, 79 insertions(+), 27 deletions(-) diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java index 745e5a400..b180fff75 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java @@ -1,12 +1,13 @@ package org.argeo.api.cms; +import java.util.Map; import java.util.Set; import org.argeo.api.cms.ux.CmsTheme; import org.argeo.api.cms.ux.CmsUi; /** An extensible user interface base on the CMS backend. */ -public interface CmsApp { +public interface CmsApp extends CmsEventSubscriber { /** * If {@link CmsUi#setData(String, Object)} is set with this property, it * indicates a different UI (typically with another theming. The {@link CmsApp} @@ -35,4 +36,10 @@ public interface CmsApp { void removeCmsAppListener(CmsAppListener listener); CmsContext getCmsContext(); + + @Override + default void onEvent(String topic, Map properties) { + } + + } diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java index 45629fba5..15b6a5dc7 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java @@ -1,8 +1,9 @@ package org.argeo.api.cms.ux; -import java.security.PrivilegedAction; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; import javax.security.auth.login.LoginContext; @@ -56,20 +57,14 @@ public interface CmsView { } - default T doAs(PrivilegedAction action) { - throw new UnsupportedOperationException(); - } - - default Void runAs(Runnable runnable) { - return doAs(new PrivilegedAction() { + /** + * Make sure that this action is executed with the proper subject and in a + * proper thread. + */ + T doAs(Callable action); - @Override - public Void run() { - if (runnable != null) - runnable.run(); - return null; - } - }); + default void runAs(Runnable runnable) { + doAs(Executors.callable(runnable)); } default void stateChanged(String state, String title) { diff --git a/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java b/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java index 415a0954d..4bfda139d 100644 --- a/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java @@ -59,9 +59,10 @@ public class LocaleUtils { /** Where the search for a message is actually performed. */ public static String local(String key, Locale locale, String resource, ClassLoader classLoader) { ResourceBundle rb = ResourceBundle.getBundle(resource, locale, classLoader); - assert key.length() > 2; - if (isLocaleKey(key)) + if (isLocaleKey(key)) { + assert key.length() > 1; key = key.substring(1); + } if (rb.containsKey(key)) return rb.getString(key); else // for simple cases, the key will actually be the English word diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java index 7fca23c99..eaa63756d 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java @@ -72,13 +72,12 @@ public class CmsEventBusImpl implements CmsEventBus { @Override public void onSubscribe(Subscription subscription) { this.subscription = subscription; - subscription.request(1); + this.subscription.request(Long.MAX_VALUE); } @Override public void onNext(Map item) { eventSubscriber.onEvent(topic, item); - subscription.request(1); } @Override diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java index 205980303..c481a2cc0 100644 --- a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java @@ -1,23 +1,29 @@ package org.argeo.cms.swt; -import java.security.PrivilegedAction; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; +import org.argeo.api.cms.CmsApp; import org.argeo.api.cms.CmsEventBus; +import org.argeo.api.cms.CmsLog; import org.argeo.api.cms.ux.CmsImageManager; import org.argeo.api.cms.ux.CmsUi; import org.argeo.api.cms.ux.CmsView; import org.argeo.api.cms.ux.UxContext; import org.argeo.cms.auth.CurrentUser; +import org.argeo.util.CurrentSubject; import org.eclipse.swt.widgets.Display; public abstract class AbstractSwtCmsView implements CmsView { + private final static CmsLog log = CmsLog.getLog(AbstractSwtCmsView.class); + protected final String uiName; protected LoginContext loginContext; @@ -37,6 +43,8 @@ public abstract class AbstractSwtCmsView implements CmsView { public abstract CmsEventBus getCmsEventBus(); + public abstract CmsApp getCmsApp(); + @Override public void sendEvent(String topic, Map properties) { if (properties == null) @@ -45,20 +53,43 @@ public abstract class AbstractSwtCmsView implements CmsView { throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid (" + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid); properties.put(CMS_VIEW_UID_PROPERTY, uid); + + log.debug(() -> uid + ": send event to " + topic); + getCmsEventBus().sendEvent(topic, properties); + //getCmsApp().onEvent(topic, properties); } - public T doAs(PrivilegedAction action) { +// public void runAs(Runnable runnable) { +// display.asyncExec(() -> doAs(Executors.callable(runnable))); +// } + + public T doAs(Callable action) { try { CompletableFuture result = new CompletableFuture<>(); Runnable toDo = () -> { - T res = Subject.doAs(getSubject(), action); + log.debug(() -> uid + ": process doAs"); + Subject subject = CurrentSubject.current(); + T res; + if (subject != null) { + assert subject == getSubject(); + try { + res = action.call(); + } catch (Exception e) { + throw new CompletionException("Failed to execute action for " + subject, e); + } + } else { + res = CurrentSubject.callAs(getSubject(), action); + } result.complete(res); }; if (Thread.currentThread() == display.getThread()) toDo.run(); - else - display.syncExec(toDo); + else { + display.asyncExec(toDo); + display.wake(); + } +// throw new IllegalStateException("Must be called from UI thread"); return result.get(); } catch (InterruptedException | ExecutionException e) { throw new IllegalStateException("Cannot execute action ins CMS view " + uid, e); diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java index 06e4d0f9f..dedf61dea 100644 --- a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java @@ -1,7 +1,7 @@ package org.argeo.cms.swt.dialogs; -import java.security.PrivilegedAction; import java.util.Arrays; +import java.util.concurrent.Callable; import org.argeo.api.cms.CmsLog; import org.argeo.api.cms.ux.CmsView; @@ -22,7 +22,7 @@ public class ChangePasswordDialog extends CmsMessageDialog { private CmsUserManager cmsUserManager; private CmsView cmsView; - private PrivilegedAction doIt; + private Callable doIt; public ChangePasswordDialog(Shell parentShell, String message, int kind, CmsUserManager cmsUserManager) { super(parentShell, message, kind); diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java index cdd489928..cda9a117f 100644 --- a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java @@ -2,6 +2,7 @@ package org.argeo.cms.e4.rap; import java.security.AccessController; import java.util.UUID; +import java.util.concurrent.Callable; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; @@ -180,4 +181,9 @@ public class CmsLoginLifecycle implements CmsView { return state; } + @Override + public T doAs(Callable action) { + throw new UnsupportedOperationException(); + } + } diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java index 3c894d158..b3ca245ad 100644 --- a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java @@ -34,12 +34,12 @@ import org.eclipse.rap.rwt.client.service.BrowserNavigation; import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent; import org.eclipse.rap.rwt.client.service.BrowserNavigationListener; import org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle; +import org.eclipse.rap.rwt.service.ServerPushSession; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTError; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Widget; /** The {@link CmsView} for a {@link CmsWebApp}. */ @SuppressWarnings("restriction") @@ -56,6 +56,8 @@ public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint, /** Experimental OS-like multi windows. */ private boolean multipleShells = false; + private ServerPushSession serverPushSession; + public CmsWebEntryPoint(CmsWebApp cmsWebApp, String uiName) { super(uiName); assert cmsWebApp != null; @@ -86,6 +88,7 @@ public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint, browserNavigation = RWT.getClient().getService(BrowserNavigation.class); if (browserNavigation != null) browserNavigation.addBrowserNavigationListener(this); + } protected void createContents(Composite parent) { @@ -108,6 +111,11 @@ public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint, ui = cmsWebApp.getCmsApp().initUi(parent); if (ui instanceof Composite) ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll()); + serverPushSession = new ServerPushSession(); + + // required in order to doAs to work + // TODO check whether it would be worth optimising + serverPushSession.start(); // we need ui to be set before refresh so that CmsView can store UI context data // in it. cmsWebApp.getCmsApp().refreshUi(ui, null); @@ -215,6 +223,11 @@ public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint, return cmsWebApp.getCmsEventBus(); } + @Override + public CmsApp getCmsApp() { + return cmsWebApp.getCmsApp(); + } + @Override public void stateChanged(String state, String title) { browserNavigation.pushState(state, title); -- 2.30.2