From: Mathieu Baudier Date: Mon, 5 Oct 2020 12:09:43 +0000 (+0200) Subject: Introduce CMS web entry point. X-Git-Tag: argeo-commons-2.1.89~83 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=ebdd232d5f7d14000c423caede5cf2b0248b234a Introduce CMS web entry point. --- diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java index 9a1e41fd1..98f9adfa0 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java @@ -2,18 +2,23 @@ package org.argeo.cms.ui; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeMap; + +import javax.jcr.Repository; import org.eclipse.rap.rwt.RWT; +/** Base class for {@link CmsApp}s. */ public abstract class AbstractCmsApp implements CmsApp { - private Map themes = Collections.synchronizedSortedMap(new TreeMap<>()); + private Map themes = Collections.synchronizedMap(new HashMap<>()); private List cmsAppListeners = new ArrayList<>(); + private Repository repository; + @Override public Set getUiNames() { // TODO Auto-generated method stub @@ -67,4 +72,12 @@ public abstract class AbstractCmsApp implements CmsApp { cmsAppListeners.remove(listener); } + protected Repository getRepository() { + return repository; + } + + public void setRepository(Repository repository) { + this.repository = repository; + } + } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java index dc5843142..3e445ce7b 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java @@ -6,9 +6,22 @@ import org.eclipse.swt.widgets.Composite; /** An extensible user interface base on the CMS backend. */ public interface CmsApp { + /** + * If {@link Composite#setData(String, Object)} is set with this property, it + * indicates a different UI (typically with another theming. The {@link CmsApp} + * can use this information, but it doesn't have to be set, in which case a + * default UI must be provided. The provided value must belong to the values + * returned by {@link CmsApp#getUiNames()}. + */ + final static String UI_NAME_PROPERTY = CmsApp.class.getName() + ".ui.name"; + Set getUiNames(); - void initUi(String uiName, Composite parent); + Composite initUi(Composite parent); + + void refreshUi(Composite parent, String state); + + void setState(Composite parent, String state); CmsTheme getTheme(String uiName); diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java index 37f52a35e..bd4daec2e 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java @@ -45,11 +45,16 @@ public interface CmsTheme { Image getIcon(String name, Integer preferredSize); static CmsTheme getCmsTheme(Composite parent) { - // find parent shell - Shell topShell = parent.getShell(); - while (topShell.getParent() != null) - topShell = (Shell) topShell.getParent(); - return (CmsTheme) topShell.getData(CmsTheme.class.getName()); + CmsTheme theme = (CmsTheme) parent.getData(CmsTheme.class.getName()); + if (theme == null) { + // find parent shell + Shell topShell = parent.getShell(); + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + theme = (CmsTheme) topShell.getData(CmsTheme.class.getName()); + parent.setData(CmsTheme.class.getName(), theme); + } + return theme; } static void registerCmsTheme(Shell shell, CmsTheme theme) { diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsFeedback.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsFeedback.java index 3ee083394..de91bc45c 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsFeedback.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsFeedback.java @@ -18,6 +18,7 @@ import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; +/** A dialog feedback based on a {@link LightweightDialog}. */ public class CmsFeedback extends LightweightDialog { private final static Log log = LogFactory.getLog(CmsFeedback.class); diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsWizardDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsWizardDialog.java index b3860f780..451287d64 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsWizardDialog.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsWizardDialog.java @@ -24,6 +24,7 @@ import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; +/** A wizard dialog based on {@link LightweightDialog}. */ public class CmsWizardDialog extends LightweightDialog implements IWizardContainer2 { private static final long serialVersionUID = -2123153353654812154L; diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/CmsLogin.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/CmsLogin.java index 4533f6498..c6a31900b 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/CmsLogin.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/CmsLogin.java @@ -116,7 +116,7 @@ public class CmsLogin implements CmsStyles, CallbackHandler { } } - protected Composite getCredentialsBlock() { + public Composite getCredentialsBlock() { return credentialsBlock; } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebApp.java b/org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebApp.java index 4c55d4eab..687b54a83 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebApp.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebApp.java @@ -11,11 +11,9 @@ import org.argeo.cms.ui.CmsAppListener; import org.argeo.cms.ui.CmsTheme; import org.argeo.util.LangUtils; import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.application.AbstractEntryPoint; import org.eclipse.rap.rwt.application.Application; import org.eclipse.rap.rwt.application.ApplicationConfiguration; import org.eclipse.rap.rwt.client.WebClient; -import org.eclipse.swt.widgets.Composite; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; @@ -69,17 +67,12 @@ public class CmsWebApp implements ApplicationConfiguration, CmsAppListener { // log.warn("Theme id " + themeId + " was specified but it was not found, using default RWT theme."); } application.addEntryPoint("/" + uiName, () -> { - return new AbstractEntryPoint() { - private static final long serialVersionUID = -9153259126766694485L; - - @Override - protected void createContents(Composite parent) { - cmsApp.initUi(uiName, parent); - - } - }; + return new CmsWebEntryPoint(this, uiName); }, properties); + if (log.isDebugEnabled()) + log.info("Added web entry point /" + (contextName != null ? contextName : "") + "/" + uiName); } + log.debug("Published CMS web app /" + (contextName != null ? contextName : "")); } // private void registerIfAllThemesAvailable() { @@ -102,7 +95,7 @@ public class CmsWebApp implements ApplicationConfiguration, CmsAppListener { // } // } - public CmsApp getCmsApp() { + CmsApp getCmsApp() { return cmsApp; } @@ -125,7 +118,8 @@ public class CmsWebApp implements ApplicationConfiguration, CmsAppListener { rwtAppReg.unregister(); if (bundleContext != null) { rwtAppReg = bundleContext.registerService(ApplicationConfiguration.class, this, regProps); - log.info("Published CMS web app /" + (contextName != null ? contextName : "")); + if (log.isDebugEnabled()) + log.debug("Publishing CMS web app /" + (contextName != null ? contextName : "") + " ..."); } } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebEntryPoint.java b/org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebEntryPoint.java new file mode 100644 index 000000000..351f771c1 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebEntryPoint.java @@ -0,0 +1,237 @@ +package org.argeo.cms.web; + +import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext; + +import java.security.PrivilegedAction; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.api.NodeConstants; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.auth.HttpRequestCallbackHandler; +import org.argeo.cms.ui.CmsApp; +import org.argeo.cms.ui.CmsImageManager; +import org.argeo.cms.ui.CmsView; +import org.argeo.cms.ui.UxContext; +import org.argeo.cms.ui.dialogs.CmsFeedback; +import org.argeo.cms.ui.util.CmsUiUtils; +import org.argeo.eclipse.ui.specific.UiContext; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.application.EntryPoint; +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.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** The {@link CmsView} for a {@link CmsWebApp}. */ +public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationListener { + private static final long serialVersionUID = 7733510691684570402L; + private final static Log log = LogFactory.getLog(CmsWebEntryPoint.class); + + private final CmsWebApp cmsWebApp; + private final String uiName; + + private LoginContext loginContext; + private String state; + private Throwable exception; + + private Composite ui; + + // Client services + // private final JavaScriptExecutor jsExecutor; + private final BrowserNavigation browserNavigation; + + /** Experimental OS-like multi windows. */ + private boolean multipleShells = false; + + public CmsWebEntryPoint(CmsWebApp cmsWebApp, String uiName) { + assert cmsWebApp != null; + assert uiName != null; + this.cmsWebApp = cmsWebApp; + this.uiName = uiName; + + // Initial login + LoginContext lc; + try { + lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, + new HttpRequestCallbackHandler(UiContext.getHttpRequest(), UiContext.getHttpResponse())); + lc.login(); + } catch (LoginException e) { + try { + lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS); + lc.login(); + } catch (LoginException e1) { + throw new IllegalStateException("Cannot log in as anonymous", e1); + } + } + authChange(lc); + + // jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class); + browserNavigation = RWT.getClient().getService(BrowserNavigation.class); + if (browserNavigation != null) + browserNavigation.addBrowserNavigationListener(this); + } + + protected void createContents(Composite parent) { + Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { + @Override + public Void run() { + try { + ui = cmsWebApp.getCmsApp().initUi(parent); + ui.setData(CmsApp.UI_NAME_PROPERTY, uiName); + ui.setLayoutData(CmsUiUtils.fillAll()); + } catch (Exception e) { + throw new IllegalStateException("Cannot create entrypoint contents", e); + } + return null; + } + }); + } + + protected Subject getSubject() { + return loginContext.getSubject(); + } + + @Override + public boolean isAnonymous() { + return CurrentUser.isAnonymous(getSubject()); + } + + @Override + public synchronized void logout() { + if (loginContext == null) + throw new IllegalArgumentException("Login context should not be null"); + try { + CurrentUser.logoutCmsSession(loginContext.getSubject()); + loginContext.logout(); + LoginContext anonymousLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS); + anonymousLc.login(); + authChange(anonymousLc); + } catch (LoginException e) { + log.error("Cannot logout", e); + } + } + + @Override + public synchronized void authChange(LoginContext lc) { + if (lc == null) + throw new IllegalArgumentException("Login context cannot be null"); + // logout previous login context + if (this.loginContext != null) + try { + this.loginContext.logout(); + } catch (LoginException e1) { + log.warn("Could not log out: " + e1); + } + this.loginContext = lc; + doRefresh(); + } + + @Override + public void exception(final Throwable e) { + exception = e; + log.error("Unexpected exception in CMS", e); + doRefresh(); + } + + protected synchronized void doRefresh() { + if (ui != null) + Subject.doAs(getSubject(), new PrivilegedAction() { + @Override + public Void run() { + if (exception != null) { + // TODO internationalise + CmsFeedback.show("Unexpected exception", exception); + exception = null; + // TODO report + } + cmsWebApp.getCmsApp().refreshUi(ui, state); + return null; + } + }); + } + + /** Sets the state of the entry point and retrieve the related JCR node. */ + protected String setState(String newState) { + cmsWebApp.getCmsApp().setState(ui, newState); + state = newState; + return null; + } + + @Override + public UxContext getUxContext() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void navigateTo(String state) { + exception = null; + String title = setState(state); + doRefresh(); + if (browserNavigation != null) + browserNavigation.pushState(state, title); + } + + @Override + public CmsImageManager getImageManager() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void navigated(BrowserNavigationEvent event) { + setState(event.getState()); + doRefresh(); + } + + /* + * EntryPoint IMPLEMENTATION + */ + + @Override + public int createUI() { + Display display = new Display(); + Shell shell = createShell(display); + shell.setLayout(CmsUiUtils.noSpaceGridLayout()); + CmsView.registerCmsView(shell, this); + createContents(shell); + shell.layout(); +// if (shell.getMaximized()) { +// shell.layout(); +// } else { +//// shell.pack(); +// } + shell.open(); + if (getApplicationContext().getLifeCycleFactory().getLifeCycle() instanceof RWTLifeCycle) { + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + display.dispose(); + } + return 0; + } + + protected Shell createShell(Display display) { + Shell shell; + if (!multipleShells) { + shell = new Shell(display, SWT.NO_TRIM); + shell.setMaximized(true); + } else { + shell = new Shell(display, SWT.SHELL_TRIM); + shell.setSize(800, 600); + } + return shell; + } + +}