Introduce CMS web entry point.
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 5 Oct 2020 12:09:43 +0000 (14:09 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 5 Oct 2020 12:09:43 +0000 (14:09 +0200)
org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java
org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsFeedback.java
org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsWizardDialog.java
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/CmsLogin.java
org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebApp.java
org.argeo.cms.ui/src/org/argeo/cms/web/CmsWebEntryPoint.java [new file with mode: 0644]

index 9a1e41fd17f238164088a1e7dd9cbe630af1725d..98f9adfa0db822e3ea00ee4699ab211e2a7480c5 100644 (file)
@@ -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<String, CmsTheme> themes = Collections.synchronizedSortedMap(new TreeMap<>());
+       private Map<String, CmsTheme> themes = Collections.synchronizedMap(new HashMap<>());
 
        private List<CmsAppListener> cmsAppListeners = new ArrayList<>();
 
+       private Repository repository;
+
        @Override
        public Set<String> 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;
+       }
+
 }
index dc5843142d681024a36e6aace18149565d888124..3e445ce7baae3d4cff00fb04b3519d92dcdb3a59 100644 (file)
@@ -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<String> 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);
 
index 37f52a35e5e40722224697f370f1bd2f63231c46..bd4daec2eec6cf53c59fad747e27a9d0681330b6 100644 (file)
@@ -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) {
index 3ee083394aa7f85c10d6c57e38c7a2288f737bdd..de91bc45cb625b0d909cd4c1a04651d110d62b86 100644 (file)
@@ -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);
 
index b3860f780ac112ce89aff4114a6894f6bef80e8d..451287d64623778a93c8c00e211086fed6162541 100644 (file)
@@ -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;
 
index 4533f649851361ad5605baa79b4d7dcc49284cfe..c6a31900b061c333d0741a0f7f7f45056b593970 100644 (file)
@@ -116,7 +116,7 @@ public class CmsLogin implements CmsStyles, CallbackHandler {
                }
        }
 
-       protected Composite getCredentialsBlock() {
+       public Composite getCredentialsBlock() {
                return credentialsBlock;
        }
 
index 4c55d4eabf11bd6ba837af4d00593b1458ff0494..687b54a83dab44a8eb2b15daca10a4820d6fb0cb 100644 (file)
@@ -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 (file)
index 0000000..351f771
--- /dev/null
@@ -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<Void>() {
+                       @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<Void>() {
+                               @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;
+       }
+
+}