Simplify CMS App deployment
authorMathieu Baudier <mbaudier@argeo.org>
Fri, 6 May 2022 10:42:17 +0000 (12:42 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Fri, 6 May 2022 10:42:17 +0000 (12:42 +0200)
20 files changed:
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java
org.argeo.cms/src/org/argeo/cms/osgi/BundleCmsTheme.java
org.argeo.util/src/org/argeo/util/OS.java
rap/org.argeo.cms.ui.rap/.project
rap/org.argeo.cms.ui.rap/OSGI-INF/cmsWebAppFactory.xml [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/bnd.bnd
rap/org.argeo.cms.ui.rap/build.properties
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpApp.xml [deleted file]
rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpDisplayFactory.xml [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpServletFactory.xml [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/bnd.bnd
rcp/org.argeo.cms.ui.rcp/build.properties
rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java
rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServlet.java [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServletFactory.java [new file with mode: 0644]
sdk/cms-e4-rcp.properties [deleted file]
sdk/cms-rcp.properties [new file with mode: 0644]

index b9b2751a7f55dd3772a1bf494a6cdd25ac897aff..8626a53e374458e84d23591b8e856158266359f0 100644 (file)
@@ -55,11 +55,18 @@ public class BundleCmsSwtTheme extends BundleCmsTheme implements CmsSwtTheme {
                        paths: for (String p : getImagesPaths()) {
                                int lastSlash = p.lastIndexOf('/');
                                String fileName = p;
+                               String ext = "";
                                if (lastSlash >= 0)
                                        fileName = p.substring(lastSlash + 1);
                                int lastDot = fileName.lastIndexOf('.');
-                               if (lastDot >= 0)
+                               if (lastDot >= 0) {
+                                       ext = fileName.substring(lastDot + 1);
                                        fileName = fileName.substring(0, lastDot);
+                               }
+
+                               if ("svg".equals(ext))
+                                       continue paths;
+
                                if (fileName.equals(name)) {// matched
                                        Image img = getImage(p);
                                        int width = img.getBounds().width;
index 761191e5dd06052842482c054751b71c6b9096ad..af9665a63bdee7dd8df3f8ba44e6c758dc27f4e6 100644 (file)
@@ -13,6 +13,8 @@ public interface CmsApp {
         */
        final static String UI_NAME_PROPERTY = CmsApp.class.getName() + ".ui.name";
 
+       final static String CONTEXT_NAME_PROPERTY = "argeo.cms.app.contextName";
+
        Set<String> getUiNames();
 
        CmsUi initUi(Object uiParent);
index 6c195d45f48c742e377f92dcaf874073e843b368..3312d72f96eefff8ffa0552c0811e0870b87cdab 100644 (file)
@@ -327,10 +327,15 @@ public class BundleCmsTheme implements CmsTheme {
        @Override
        public InputStream loadPath(String path) throws IOException {
                URL url = themeBundle.getResource(path);
-               if (url == null)
-                       throw new IllegalArgumentException(
-                                       "Path " + path + " not found in bundle " + themeBundle.getSymbolicName());
-               return url.openStream();
+               if (url == null) {
+                       if (parentTheme != null)
+                               return parentTheme.loadPath(path);
+                       else
+                               throw new IllegalArgumentException(
+                                               "Path " + path + " not found in bundle " + themeBundle.getSymbolicName());
+               } else {
+                       return url.openStream();
+               }
        }
 
        private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) {
index d8127b6007e25e63ce52cbe6090839fd91026cfd..caf96dd8cd83a52394d6d67a1b4ea5bab74a1052 100644 (file)
@@ -1,7 +1,8 @@
 package org.argeo.util;
 
 import java.io.File;
-import java.lang.management.ManagementFactory;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 
 /** When OS specific informations are needed. */
 public class OS {
@@ -40,17 +41,25 @@ public class OS {
                        return new String[] { "cmd.exe", "/C" };
        }
 
-       public static Integer getJvmPid() {
-               /*
-                * This method works on most platforms (including Linux). Although when Java 9
-                * comes along, there is a better way: long pid =
-                * ProcessHandle.current().getPid();
-                *
-                * See:
-                * http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-
-                * process-id
-                */
-               String pidAndHost = ManagementFactory.getRuntimeMXBean().getName();
-               return Integer.parseInt(pidAndHost.substring(0, pidAndHost.indexOf('@')));
+       public static long getJvmPid() {
+               return ProcessHandle.current().pid();
+//             String pidAndHost = ManagementFactory.getRuntimeMXBean().getName();
+//             return Integer.parseInt(pidAndHost.substring(0, pidAndHost.indexOf('@')));
+       }
+
+       /**
+        * Get the runtime directory. It will be the environment variable
+        * XDG_RUNTIME_DIR if it is set, or ~/.cache/argeo if not.
+        */
+       public static Path getRunDir() {
+               Path runDir;
+               String xdgRunDir = System.getenv("XDG_RUNTIME_DIR");
+               if (xdgRunDir != null) {
+                       // TODO support multiple names
+                       runDir = Paths.get(xdgRunDir);
+               } else {
+                       runDir = Paths.get(System.getProperty("user.home"), ".cache/argeo");
+               }
+               return runDir;
        }
 }
index 1a37a677192bcaf0aefa98e3be33af3e812fff93..63016040289791809506a15ce8b049af4f691e75 100644 (file)
                        <arguments>
                        </arguments>
                </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
        </buildSpec>
        <natures>
                <nature>org.eclipse.pde.PluginNature</nature>
diff --git a/rap/org.argeo.cms.ui.rap/OSGI-INF/cmsWebAppFactory.xml b/rap/org.argeo.cms.ui.rap/OSGI-INF/cmsWebAppFactory.xml
new file mode 100644 (file)
index 0000000..aa7e0ad
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="CMS Web App Factory">
+   <implementation class="org.argeo.cms.web.osgi.CmsWebAppFactory"/>
+   <reference bind="addCmsApp" cardinality="0..n" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic" unbind="removeCmsApp"/>
+   <reference bind="setEventAdmin" cardinality="1..1" interface="org.osgi.service.event.EventAdmin" name="EventAdmin" policy="static"/>
+</scr:component>
index e3e71b30937e47a01f8e45044ae994d37ee781da..dc528df8cd3bf3d41622be2c33392ff0cffbf326 100644 (file)
@@ -13,3 +13,5 @@ org.eclipse.jetty.*;version="[9.4,12)";resolution:=optional,\
 javax.servlet.*;version="[3,5)",\
 *
 
+Service-Component: OSGI-INF/cmsWebAppFactory.xml
+
index 34d2e4d2dad529ceaeb953bfcdb63c51d69ffed2..7d543b56b0a15320fc9acfd6950c175f8155c320 100644 (file)
@@ -1,4 +1,5 @@
-source.. = src/
 output.. = bin/
 bin.includes = META-INF/,\
-               .
+               .,\
+               OSGI-INF/cmsWebAppFactory.xml
+source.. = src/
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java
new file mode 100644 (file)
index 0000000..8380a85
--- /dev/null
@@ -0,0 +1,54 @@
+package org.argeo.cms.web.osgi;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.cms.web.CmsWebApp;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.event.EventAdmin;
+
+/** Publish a CmsApp as a RAP application. */
+public class CmsWebAppFactory {
+       private BundleContext bundleContext = FrameworkUtil.getBundle(CmsWebAppFactory.class).getBundleContext();
+       private final static String CONTEXT_NAME = "contextName";
+
+       private EventAdmin eventAdmin;
+
+       private Map<String, CmsWebApp> registrations = Collections.synchronizedMap(new HashMap<>());
+
+       public void addCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               if (contextName != null) {
+                       CmsWebApp cmsWebApp = new CmsWebApp();
+                       cmsWebApp.setEventAdmin(eventAdmin);
+                       cmsWebApp.setCmsApp(cmsApp, properties);
+                       Hashtable<String, String> serviceProperties = new Hashtable<>();
+                       if (!contextName.equals(""))
+                               serviceProperties.put(CONTEXT_NAME, contextName);
+                       cmsWebApp.init(bundleContext, serviceProperties);
+                       registrations.put(contextName, cmsWebApp);
+               }
+       }
+
+       public void removeCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               if (contextName != null) {
+                       CmsWebApp cmsWebApp = registrations.get(contextName);
+                       if (cmsWebApp != null) {
+                               cmsWebApp.destroy(bundleContext, new HashMap<>());
+                               cmsWebApp.unsetCmsApp(cmsApp, properties);
+                       } else {
+                               // TODO log warning
+                       }
+               }
+       }
+
+       public void setEventAdmin(EventAdmin eventAdmin) {
+               this.eventAdmin = eventAdmin;
+       }
+
+}
diff --git a/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpApp.xml b/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpApp.xml
deleted file mode 100644 (file)
index 490606f..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="CMS RCP App">
-   <implementation class="org.argeo.cms.ui.rcp.CmsRcpApp"/>
-   <reference bind="setCmsApp" cardinality="1..1" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic"/>
-   <reference bind="setEventAdmin" cardinality="1..1" interface="org.osgi.service.event.EventAdmin" name="EventAdmin" policy="static"/>
-   <reference bind="setCmsContext" cardinality="1..1" interface="org.argeo.api.cms.CmsContext" name="CmsContext" policy="static"/>
-</scr:component>
diff --git a/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpDisplayFactory.xml b/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpDisplayFactory.xml
new file mode 100644 (file)
index 0000000..a0c0f0f
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="CMS RCP Display Factory">
+   <implementation class="org.argeo.cms.ui.rcp.CmsRcpDisplayFactory"/>
+</scr:component>
diff --git a/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpServletFactory.xml b/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpServletFactory.xml
new file mode 100644 (file)
index 0000000..a1f0b3a
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="CMS RCP Servlet Factory">
+   <implementation class="org.argeo.cms.ui.rcp.servlet.CmsRcpServletFactory"/>
+   <reference bind="addCmsApp" cardinality="0..n" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic" unbind="removeCmsApp"/>
+   <reference bind="setEventAdmin" cardinality="1..1" interface="org.osgi.service.event.EventAdmin" name="EventAdmin" policy="static"/>
+   <reference bind="setHttpService" cardinality="1..1" interface="org.osgi.service.http.HttpService" name="HttpService" policy="static"/>
+</scr:component>
index 72b00d2cdc080abd94db1a6d268730b0fa029969..91f0a8a3718eb5cb4d1a5140d6fe8bca8bc160f1 100644 (file)
@@ -1,9 +1,12 @@
 
-Service-Component: OSGI-INF/cmsRcpApp.xml
-
 Import-Package:\
 org.argeo.cms.auth,\
 org.eclipse.swt,\
 org.eclipse.swt.graphics,\
 org.w3c.css.sac,\
 *
+
+Service-Component:\
+OSGI-INF/cmsRcpDisplayFactory.xml,\
+OSGI-INF/cmsRcpServletFactory.xml
+
index 6210e849b591d26fa9e17057ad3e8d09511917b6..5eef7058e47f4318354d9b883628b2ce2723114c 100644 (file)
@@ -1,5 +1,6 @@
 output.. = bin/
 bin.includes = META-INF/,\
                .,\
-               OSGI-INF/
+               OSGI-INF/,\
+               OSGI-INF/cmsRcpServletFactory.xml
 source.. = src/
index fb56773b86bb8bac3744d09e71766045fea377e5..664d49d2b54896d99f0038b861cdd09bd4b5562a 100644 (file)
@@ -13,7 +13,6 @@ import javax.security.auth.login.LoginException;
 
 import org.argeo.api.cms.CmsApp;
 import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsContext;
 import org.argeo.api.cms.CmsImageManager;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsSession;
@@ -26,7 +25,6 @@ import org.argeo.cms.swt.CmsSwtUtils;
 import org.eclipse.e4.ui.css.core.engine.CSSEngine;
 import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler;
 import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl;
-import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Shell;
@@ -42,13 +40,9 @@ public class CmsRcpApp implements CmsView {
 
        private BundleContext bundleContext = FrameworkUtil.getBundle(CmsRcpApp.class).getBundleContext();
 
-       private Display display;
        private Shell shell;
        private CmsApp cmsApp;
-       private CmsUiThread uiThread;
 
-       private CmsContext cmsContext;
-       
        // CMS View
        private String uid;
        private LoginContext loginContext;
@@ -61,123 +55,68 @@ public class CmsRcpApp implements CmsView {
        // TODO make it configurable
        private String uiName = "desktop";
 
-       public CmsRcpApp() {
+       public CmsRcpApp(String uiName) {
                uid = UUID.randomUUID().toString();
+               this.uiName = uiName;
        }
 
-       public void init(Map<String, String> properties) {
-               try {
-                       Thread.sleep(5000);
-               } catch (InterruptedException e) {
-                       // silent
-               }
-               uiThread = new CmsUiThread();
-               uiThread.start();
-
-       }
+       public void initRcpApp() {
+               Display display = Display.getCurrent();
+               shell = new Shell(display);
+               shell.setText("Argeo CMS");
+               Composite parent = shell;
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               CmsSwtUtils.registerCmsView(shell, CmsRcpApp.this);
 
-       public void destroy(Map<String, String> properties) {
-               if (!shell.isDisposed())
-                       shell.dispose();
                try {
-                       uiThread.join();
-               } catch (InterruptedException e) {
-                       // silent
-               } finally {
-                       uiThread = null;
+                       loginContext = new LoginContext(CmsAuth.SINGLE_USER.getLoginContextName());
+                       loginContext.login();
+               } catch (LoginException e) {
+                       throw new IllegalStateException("Could not log in.", e);
                }
-       }
-
-       class CmsUiThread extends Thread {
-
-               public CmsUiThread() {
-                       super("CMS UI");
-               }
-
-               @Override
-               public void run() {
-                       display = Display.getDefault();
-                       shell = new Shell(display);
-                       shell.setText("Argeo CMS");
-                       Composite parent = shell;
-                       parent.setLayout(new GridLayout());
-                       CmsSwtUtils.registerCmsView(shell, CmsRcpApp.this);
-
-//                     Subject subject = new Subject();
-//                     CmsLoginShell loginShell = new CmsLoginShell(CmsRcpApp.this);
-//                     loginShell.setSubject(subject);
-                       try {
-                               // try pre-auth
-//                             loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject, loginShell);
-                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_SINGLE_USER);
-                               loginContext.login();
-                       } catch (LoginException e) {
-                               throw new IllegalStateException("Could not log in.", e);
-//                             loginShell.createUi();
-//                             loginShell.open();
-//
-//                             while (!loginShell.getShell().isDisposed()) {
-//                                     if (!display.readAndDispatch())
-//                                             display.sleep();
-//                             }
-                       }
-                       if (log.isDebugEnabled())
-                               log.debug("Logged in to desktop: " + loginContext.getSubject());
-
-                       Subject.doAs(loginContext.getSubject(), (PrivilegedAction<Void>) () -> {
-
-                               // TODO factorise with web app
-                               parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
-                               ui = cmsApp.initUi(parent);
-                               if (ui instanceof Composite)
-                                       ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll());
-                               // ui.setLayoutData(CmsUiUtils.fillAll());
-                               // we need ui to be set before refresh so that CmsView can store UI context data
-                               // in it.
-                               cmsApp.refreshUi(ui, null);
-
-                               // Styling
-                               CmsTheme theme = CmsSwtUtils.getCmsTheme(parent);
-                               if (theme != null) {
-                                       cssEngine = new CSSSWTEngineImpl(display);
-                                       for (String path : theme.getSwtCssPaths()) {
-                                               try (InputStream in = theme.loadPath(path)) {
-                                                       cssEngine.parseStyleSheet(in);
-                                               } catch (IOException e) {
-                                                       throw new IllegalStateException("Cannot load stylesheet " + path, e);
-                                               }
+               if (log.isDebugEnabled())
+                       log.debug("Logged in to desktop: " + loginContext.getSubject());
+
+               Subject.doAs(loginContext.getSubject(), (PrivilegedAction<Void>) () -> {
+
+                       // TODO factorise with web app
+                       parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
+                       ui = cmsApp.initUi(parent);
+                       if (ui instanceof Composite)
+                               ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll());
+                       // we need ui to be set before refresh so that CmsView can store UI context data
+                       // in it.
+                       cmsApp.refreshUi(ui, null);
+
+                       // Styling
+                       CmsTheme theme = CmsSwtUtils.getCmsTheme(parent);
+                       if (theme != null) {
+                               cssEngine = new CSSSWTEngineImpl(display);
+                               for (String path : theme.getSwtCssPaths()) {
+                                       try (InputStream in = theme.loadPath(path)) {
+                                               cssEngine.parseStyleSheet(in);
+                                       } catch (IOException e) {
+                                               throw new IllegalStateException("Cannot load stylesheet " + path, e);
                                        }
-                                       cssEngine.setErrorHandler(new CSSErrorHandler() {
-                                               public void error(Exception e) {
-                                                       log.error("SWT styling error: ", e);
-                                               }
-                                       });
-                                       applyStyles(shell);
-                               }
-                               shell.layout(true, true);
-
-                               shell.open();
-                               while (!shell.isDisposed()) {
-                                       if (!display.readAndDispatch())
-                                               display.sleep();
                                }
-                               display.dispose();
-                               return null;
-                       });
-               }
+                               cssEngine.setErrorHandler(new CSSErrorHandler() {
+                                       public void error(Exception e) {
+                                               log.error("SWT styling error: ", e);
+                                       }
+                               });
+                               applyStyles(shell);
+                       }
+                       shell.layout(true, true);
 
+                       shell.open();
+                       return null;
+               });
        }
 
-       
-       
        /*
         * CMS VIEW
         */
 
-       public void setCmsContext(CmsContext cmsContext) {
-               this.cmsContext = cmsContext;
-       }
-
        @Override
        public String getUid() {
                return uid;
@@ -274,10 +213,14 @@ public class CmsRcpApp implements CmsView {
        /*
         * DEPENDENCY INJECTION
         */
-       public void setCmsApp(CmsApp cmsApp) {
+       public void setCmsApp(CmsApp cmsApp, Map<String, String> properties) {
                this.cmsApp = cmsApp;
        }
 
+       public void unsetCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               this.cmsApp = null;
+       }
+
        public void setEventAdmin(EventAdmin eventAdmin) {
                this.eventAdmin = eventAdmin;
        }
diff --git a/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java b/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java
new file mode 100644 (file)
index 0000000..ceab08a
--- /dev/null
@@ -0,0 +1,79 @@
+package org.argeo.cms.ui.rcp;
+
+import java.nio.file.Path;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.util.OS;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.event.EventAdmin;
+
+/** Creates the SWT {@link Display} in a dedicated thread. */
+public class CmsRcpDisplayFactory {
+       /** File name in a run directory */
+       private final static String ARGEO_RCP_URL = "argeo.rcp.url";
+
+       /** There is only one display in RCP mode */
+       private static Display display;
+
+       private CmsUiThread uiThread;
+
+       private boolean shutdown = false;
+
+       public void init() {
+               uiThread = new CmsUiThread();
+               uiThread.start();
+
+       }
+
+       public void destroy() {
+               shutdown = true;
+               display.wake();
+               try {
+                       uiThread.join();
+               } catch (InterruptedException e) {
+                       // silent
+               } finally {
+                       uiThread = null;
+               }
+       }
+
+       class CmsUiThread extends Thread {
+
+               public CmsUiThread() {
+                       super("CMS UI");
+               }
+
+               @Override
+               public void run() {
+                       display = Display.getDefault();
+
+//                     for (String contextName : cmsApps.keySet()) {
+//                             openCmsApp(contextName);
+//                     }
+
+                       while (!shutdown) {
+                               if (!display.readAndDispatch())
+                                       display.sleep();
+                       }
+                       display.dispose();
+                       display = null;
+               }
+       }
+
+       public static Display getDisplay() {
+               return display;
+       }
+
+       public static void openCmsApp(EventAdmin eventAdmin, CmsApp cmsApp, String uiName) {
+               CmsRcpDisplayFactory.getDisplay().syncExec(() -> {
+                       CmsRcpApp cmsRcpApp = new CmsRcpApp(uiName);
+                       cmsRcpApp.setEventAdmin(eventAdmin);
+                       cmsRcpApp.setCmsApp(cmsApp, null);
+                       cmsRcpApp.initRcpApp();
+               });
+       }
+
+       public static Path getUrlRunFile() {
+               return OS.getRunDir().resolve(CmsRcpDisplayFactory.ARGEO_RCP_URL);
+       }
+}
diff --git a/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServlet.java b/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServlet.java
new file mode 100644 (file)
index 0000000..54e2165
--- /dev/null
@@ -0,0 +1,40 @@
+package org.argeo.cms.ui.rcp.servlet;
+
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.Objects;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.cms.ui.rcp.CmsRcpDisplayFactory;
+import org.osgi.service.event.EventAdmin;
+
+/** Open the related app when called. */
+public class CmsRcpServlet extends HttpServlet {
+       private static final long serialVersionUID = -3944472431354848923L;
+       private final static Logger logger = System.getLogger(CmsRcpServlet.class.getName());
+
+       private CmsApp cmsApp;
+       private EventAdmin eventAdmin;
+
+       public CmsRcpServlet(EventAdmin eventAdmin, CmsApp cmsApp) {
+               Objects.requireNonNull(eventAdmin);
+               Objects.requireNonNull(cmsApp);
+               this.cmsApp = cmsApp;
+               this.eventAdmin = eventAdmin;
+       }
+
+       @Override
+       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               String path = req.getPathInfo();
+               String uiName = path.substring(path.lastIndexOf('/') + 1);
+               CmsRcpDisplayFactory.openCmsApp(eventAdmin, cmsApp, uiName);
+               logger.log(Level.DEBUG, "Opened RCP UI  " + uiName + " of  CMS App " + req.getServletPath());
+       }
+
+}
diff --git a/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServletFactory.java b/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServletFactory.java
new file mode 100644 (file)
index 0000000..5a199d8
--- /dev/null
@@ -0,0 +1,132 @@
+package org.argeo.cms.ui.rcp.servlet;
+
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.net.DatagramSocket;
+import java.net.ServerSocket;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import javax.servlet.Servlet;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.cms.ui.rcp.CmsRcpDisplayFactory;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.http.HttpService;
+
+/** Publishes one {@link CmsRcpServlet} per {@link CmsApp}. */
+public class CmsRcpServletFactory {
+       private final static Logger logger = System.getLogger(CmsRcpServletFactory.class.getName());
+
+       private BundleContext bundleContext = FrameworkUtil.getBundle(CmsRcpServletFactory.class).getBundleContext();
+
+       private CompletableFuture<EventAdmin> eventAdmin = new CompletableFuture<>();
+
+       private Map<String, ServiceRegistration<Servlet>> registrations = Collections.synchronizedMap(new HashMap<>());
+
+       public void init() {
+
+       }
+
+       public void destroy() {
+               Path runFile = CmsRcpDisplayFactory.getUrlRunFile();
+               try {
+                       if (Files.exists(runFile)) {
+                               Files.delete(runFile);
+                       }
+               } catch (IOException e) {
+                       logger.log(Level.ERROR, "Cannot delete " + runFile, e);
+               }
+       }
+
+       public void addCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               if (contextName != null) {
+                       eventAdmin.thenAccept((eventAdmin) -> {
+                               CmsRcpServlet servlet = new CmsRcpServlet(eventAdmin, cmsApp);
+                               Hashtable<String, String> serviceProperties = new Hashtable<>();
+                               serviceProperties.put("osgi.http.whiteboard.servlet.pattern", "/" + contextName + "/*");
+                               ServiceRegistration<Servlet> sr = bundleContext.registerService(Servlet.class, servlet,
+                                               serviceProperties);
+                               registrations.put(contextName, sr);
+                       });
+               }
+       }
+
+       public void removeCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               if (contextName != null) {
+                       ServiceRegistration<Servlet> sr = registrations.get(contextName);
+                       sr.unregister();
+               }
+       }
+
+       public void setEventAdmin(EventAdmin eventAdmin) {
+               this.eventAdmin.complete(eventAdmin);
+       }
+
+       public void setHttpService(HttpService httpService, Map<String, Object> properties) {
+               Integer httpPort = Integer.parseInt(properties.get("http.port").toString());
+               String baseUrl = "http://localhost:" + httpPort + "/";
+               Path runFile = CmsRcpDisplayFactory.getUrlRunFile();
+               try {
+                       if (!Files.exists(runFile)) {
+                               Files.createDirectories(runFile.getParent());
+                               // TODO give read permission only to the owner
+                               Files.createFile(runFile);
+                       } else {
+                               URI uri = URI.create(Files.readString(runFile));
+                               if (!httpPort.equals(uri.getPort()))
+                                       if (!isPortAvailable(uri.getPort())) {
+                                               throw new IllegalStateException("Another CMS is running on " + runFile);
+                                       } else {
+                                               logger.log(Level.WARNING,
+                                                               "Run file " + runFile + " found but port of " + uri + " is available. Overwriting...");
+                                       }
+                       }
+                       Files.writeString(runFile, baseUrl, StandardCharsets.UTF_8);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot write run file to " + runFile, e);
+               }
+               logger.log(Level.DEBUG, "RCP available under " + baseUrl + ", written to " + runFile);
+       }
+
+       protected boolean isPortAvailable(int port) {
+               ServerSocket ss = null;
+               DatagramSocket ds = null;
+               try {
+                       ss = new ServerSocket(port);
+                       ss.setReuseAddress(true);
+                       ds = new DatagramSocket(port);
+                       ds.setReuseAddress(true);
+                       return true;
+               } catch (IOException e) {
+               } finally {
+                       if (ds != null) {
+                               ds.close();
+                       }
+
+                       if (ss != null) {
+                               try {
+                                       ss.close();
+                               } catch (IOException e) {
+                                       /* should not be thrown */
+                               }
+                       }
+               }
+
+               return false;
+       }
+}
diff --git a/sdk/cms-e4-rcp.properties b/sdk/cms-e4-rcp.properties
deleted file mode 100644 (file)
index df8363b..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-argeo.osgi.start.2.node=\
-org.eclipse.equinox.metatype,\
-org.eclipse.equinox.cm,\
-org.eclipse.equinox.ds,\
-org.argeo.init
-
-argeo.osgi.start.3.node=\
-org.argeo.cms,\
-org.argeo.cms.jcr,\
-org.argeo.cms.ui.rcp
-
-
-# Local
-argeo.node.repo.type=h2
-org.osgi.service.http.port=7070
-#org.osgi.service.http.port.secure=7073
-
-argeo.node.useradmin.uris=os:///
-
-#argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@localhost:10389/dc=example,dc=com
-
-argeo.node.init=../../init
-
-argeo.i18n.locales=en,fr
-argeo.i18n.defaultLocale=en
-
-#tika.config=/home/mbaudier/dev/git/gpl/argeo-suite/sdk/exec/argeo-office-e4-rap/data/indexes/node/tika-config.xml
-
-# Logging
-log.org.argeo=DEBUG
-
-# DON'T CHANGE BELOW
-org.eclipse.equinox.http.jetty.autostart=false
-org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
-com.sun.jndi.ldap.sasl,\
-com.sun.security.jgss,\
-com.sun.jndi.dns,\
-com.sun.nio.file,\
-com.sun.nio.sctp
diff --git a/sdk/cms-rcp.properties b/sdk/cms-rcp.properties
new file mode 100644 (file)
index 0000000..df8363b
--- /dev/null
@@ -0,0 +1,39 @@
+argeo.osgi.start.2.node=\
+org.eclipse.equinox.metatype,\
+org.eclipse.equinox.cm,\
+org.eclipse.equinox.ds,\
+org.argeo.init
+
+argeo.osgi.start.3.node=\
+org.argeo.cms,\
+org.argeo.cms.jcr,\
+org.argeo.cms.ui.rcp
+
+
+# Local
+argeo.node.repo.type=h2
+org.osgi.service.http.port=7070
+#org.osgi.service.http.port.secure=7073
+
+argeo.node.useradmin.uris=os:///
+
+#argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@localhost:10389/dc=example,dc=com
+
+argeo.node.init=../../init
+
+argeo.i18n.locales=en,fr
+argeo.i18n.defaultLocale=en
+
+#tika.config=/home/mbaudier/dev/git/gpl/argeo-suite/sdk/exec/argeo-office-e4-rap/data/indexes/node/tika-config.xml
+
+# Logging
+log.org.argeo=DEBUG
+
+# DON'T CHANGE BELOW
+org.eclipse.equinox.http.jetty.autostart=false
+org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
+com.sun.jndi.ldap.sasl,\
+com.sun.security.jgss,\
+com.sun.jndi.dns,\
+com.sun.nio.file,\
+com.sun.nio.sctp