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;
*/
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);
@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) {
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 {
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;
}
}
<arguments>
</arguments>
</buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ds.core.builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
--- /dev/null
+<?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>
javax.servlet.*;version="[3,5)",\
*
+Service-Component: OSGI-INF/cmsWebAppFactory.xml
+
-source.. = src/
output.. = bin/
bin.includes = META-INF/,\
- .
+ .,\
+ OSGI-INF/cmsWebAppFactory.xml
+source.. = src/
--- /dev/null
+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;
+ }
+
+}
+++ /dev/null
-<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
-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
+
output.. = bin/
bin.includes = META-INF/,\
.,\
- OSGI-INF/
+ OSGI-INF/,\
+ OSGI-INF/cmsRcpServletFactory.xml
source.. = src/
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;
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;
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;
// 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;
/*
* 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;
}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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());
+ }
+
+}
--- /dev/null
+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;
+ }
+}
+++ /dev/null
-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
--- /dev/null
+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