package org.argeo.api.cms;
+import java.util.concurrent.CompletionStage;
+
+import com.sun.net.httpserver.HttpServer;
+
/** A configured node deployment. */
public interface CmsDeployment {
-
-// void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props);
-//
-// Dictionary<String, Object> getProps(String factoryPid, String cn);
+ /** The local HTTP server, or null if none is expected. */
+ CompletionStage<HttpServer> getHttpServer();
+
+ /** The local SSH server, or null if none is expected. */
+ CompletionStage<CmsSshd> getCmsSshd();
}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.net.InetSocketAddress;
+
+/** A local SSH server. */
+public interface CmsSshd {
+ final static String NODE_USERNAME_ALIAS = "user.name";
+ final static String DEFAULT_SSH_HOST_KEY_PATH = "private/" + CmsConstants.NODE + ".ser";
+
+ InetSocketAddress getAddress();
+}
package org.argeo.api.cms.ux;
+import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import org.argeo.api.cms.CmsSession;
-/** Provides interaction with the CMS system. */
+/** Provides UX interactions with the CMS system. */
public interface CmsView {
final static String CMS_VIEW_UID_PROPERTY = "argeo.cms.view.uid";
// String KEY = "org.argeo.cms.ui.view";
// SERVICES
void exception(Throwable e);
- <V,M> CmsImageManager<V, M> getImageManager();
+ <V, M> CmsImageManager<V, M> getImageManager();
boolean isAnonymous();
+ /**
+ * Translates to an URL that can be reached by a client, depending on its type.
+ * Typically, if a web interface asks for /path/on/the/web/server it will be
+ * returned without modifications; but a thin client will probably need to add a
+ * server and a port.
+ */
+ URI toBackendUri(String url);
+
/**
* Send an event to this topic. Does nothing by default., but if implemented it
* MUST set the {@link #CMS_VIEW_UID_PROPERTY} in the properties.
import java.io.IOException;
import java.net.InetSocketAddress;
+import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
+import javax.net.ssl.SSLContext;
import javax.servlet.ServletException;
import javax.websocket.server.ServerContainer;
}
if (httpsEnabled) {
+ if (httpsConfigurator == null) {
+ // we make sure that an HttpSConfigurator is set, so that clients can detect
+ // whether this server is HTTP or HTTPS
+ try {
+ httpsConfigurator = new HttpsConfigurator(SSLContext.getDefault());
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("Cannot initalise SSL Context", e);
+ }
+ }
+
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
// sslContextFactory.setKeyStore(KeyS)
<implementation class="org.argeo.cms.ssh.CmsSshServer"/>
<reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
<service>
- <provide interface="org.argeo.cms.CmsSshd"/>
+ <provide interface="org.argeo.api.cms.CmsSshd"/>
</service>
</scr:component>
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
+import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.argeo.api.cms.CmsAuth;
import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSshd;
import org.argeo.api.cms.CmsState;
import org.argeo.cms.CmsDeployProperty;
-import org.argeo.cms.CmsSshd;
public class CmsSshServer implements CmsSshd {
private final static CmsLog log = CmsLog.getLog(CmsSshServer.class);
this.cmsState = cmsState;
}
+ @Override
+ public InetSocketAddress getAddress() {
+ return new InetSocketAddress(host, port);
+ }
+
}
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="true" name="CMS Deployment">
<implementation class="org.argeo.cms.internal.runtime.CmsDeploymentImpl"/>
<reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
- <reference bind="setCmsSshd" cardinality="0..1" interface="org.argeo.cms.CmsSshd" policy="dynamic"/>
+ <reference bind="setCmsSshd" cardinality="0..1" interface="org.argeo.api.cms.CmsSshd" policy="dynamic"/>
<reference bind="setHttpServer" cardinality="0..1" interface="com.sun.net.httpserver.HttpServer" policy="dynamic"/>
<reference bind="addHttpHandler" unbind="removeHttpHandler" cardinality="0..n" interface="com.sun.net.httpserver.HttpHandler" policy="dynamic"/>
<service>
+++ /dev/null
-package org.argeo.cms;
-
-import org.argeo.api.cms.CmsConstants;
-
-/** Just a marker interface for the time being. */
-public interface CmsSshd {
- final static String NODE_USERNAME_ALIAS = "user.name";
- final static String DEFAULT_SSH_HOST_KEY_PATH = "private/" + CmsConstants.NODE + ".ser";
-}
import static org.argeo.api.cms.CmsConstants.CONTEXT_PATH;
import java.util.Map;
+import java.util.Objects;
import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsDeployment;
import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSshd;
import org.argeo.api.cms.CmsState;
import org.argeo.cms.CmsDeployProperty;
-import org.argeo.cms.CmsSshd;
import org.argeo.cms.internal.http.CmsAuthenticator;
import org.argeo.cms.internal.http.PublicCmsAuthenticator;
private boolean sshdExpected = false;
// HTTP
- private HttpServer httpServer;
+ private CompletableFuture<HttpServer> httpServer = new CompletableFuture<>();
private Map<String, HttpHandler> httpHandlers = new TreeMap<>();
private Map<String, CmsAuthenticator> httpAuthenticators = new TreeMap<>();
// SSHD
- private CmsSshd cmsSshd;
+ private CompletableFuture<CmsSshd> cmsSshd = new CompletableFuture<>();
public void start() {
log.debug(() -> "CMS deployment available");
String httpPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTP_PORT.getProperty());
String httpsPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTPS_PORT.getProperty());
httpExpected = httpPort != null || httpsPort != null;
+ if (!httpExpected)
+ httpServer.complete(null);
String sshdPort = this.cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty());
sshdExpected = sshdPort != null;
+ if (!sshdExpected)
+ cmsSshd.complete(null);
}
public void setHttpServer(HttpServer httpServer) {
- this.httpServer = httpServer;
+ Objects.requireNonNull(httpServer);
+ this.httpServer.complete(httpServer);
// create contexts whose handles had already been published
for (String contextPath : httpHandlers.keySet()) {
HttpHandler httpHandler = httpHandlers.get(contextPath);
}
public void createHttpContext(String contextPath, HttpHandler httpHandler, CmsAuthenticator authenticator) {
- HttpContext httpContext = httpServer.createContext(contextPath);
+ HttpContext httpContext = httpServer.join().createContext(contextPath);
// we want to set the authenticator BEFORE the handler actually becomes active
httpContext.setAuthenticator(authenticator);
httpContext.setHandler(httpHandler);
httpHandlers.remove(contextPath);
if (httpServer == null)
return;
- httpServer.removeContext(contextPath);
+ httpServer.join().removeContext(contextPath);
log.debug(() -> "Removed handler " + contextPath + " : " + httpHandler.getClass().getName());
}
}
public void setCmsSshd(CmsSshd cmsSshd) {
- this.cmsSshd = cmsSshd;
+ Objects.requireNonNull(cmsSshd);
+ this.cmsSshd.complete(cmsSshd);
+ }
+
+ @Override
+ public CompletionStage<HttpServer> getHttpServer() {
+ return httpServer.minimalCompletionStage();
+ }
+
+ @Override
+ public CompletionStage<CmsSshd> getCmsSshd() {
+ return cmsSshd.minimalCompletionStage();
}
}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" activate="start" deactivate="stop" name="Jetty Service Factory">
+ <implementation class="org.argeo.cms.equinox.http.jetty.EquinoxJettyServer"/>
+ <property name="service.pid" type="String" value="org.argeo.equinox.jetty.config"/>
+ <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+ <service>
+ <provide interface="com.sun.net.httpserver.HttpServer"/>
+ <provide interface="com.sun.net.httpserver.HttpsServer"/>
+ </service>
+</scr:component>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" activate="start" deactivate="stop" name="Jetty Service Factory">
- <implementation class="org.argeo.cms.equinox.http.jetty.EquinoxJettyServer"/>
- <property name="service.pid" type="String" value="org.argeo.equinox.jetty.config"/>
- <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
- <service>
- <provide interface="com.sun.net.httpserver.HttpServer"/>
- </service>
-</scr:component>
Service-Component: \
-OSGI-INF/jettyServiceFactory.xml,\
+OSGI-INF/equinoxJettyServer.xml,\
import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.security.PrivilegedAction;
import java.util.Locale;
import java.util.UUID;
return cmsSession;
}
+ @Override
+ public URI toBackendUri(String url) {
+ try {
+ return new URI(url);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot convert " + url, e);
+ }
+ }
+
/*
* EntryPoint IMPLEMENTATION
*/
<implementation class="org.argeo.cms.ui.rcp.dbus.CmsRcpDBusLauncher"/>
<reference bind="setCmsDBus" cardinality="1..1" interface="org.argeo.cms.dbus.CmsDBus" name="CmsDBus" policy="static"/>
<reference bind="addCmsApp" cardinality="0..n" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic" unbind="removeCmsApp"/>
+ <reference bind="setCmsRcpDisplayFactory" cardinality="1..1" interface="org.argeo.cms.ui.rcp.CmsRcpDisplayFactory" name="CmsRcpDisplayFactory" policy="static"/>
</scr:component>
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="true" name="CMS RCP Display Factory">
<implementation class="org.argeo.cms.ui.rcp.CmsRcpDisplayFactory"/>
+ <service>
+ <provide interface="org.argeo.cms.ui.rcp.CmsRcpDisplayFactory"/>
+ </service>
+ <reference bind="setCmsDeployment" cardinality="1..1" interface="org.argeo.api.cms.CmsDeployment" name="CmsDeployment" policy="static"/>
</scr:component>
Import-Package:\
org.argeo.cms.auth,\
+org.argeo.api.acr,\
org.eclipse.swt,\
org.eclipse.swt.widgets,\
org.eclipse.swt.graphics,\
import java.io.IOException;
import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.UUID;
import org.argeo.api.cms.CmsEventBus;
import org.argeo.api.cms.CmsLog;
import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.ux.CmsImageManager;
import org.argeo.api.cms.ux.CmsTheme;
import org.argeo.api.cms.ux.CmsView;
import org.argeo.cms.swt.AbstractSwtCmsView;
import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.AcrSwtImageManager;
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;
private CSSEngine cssEngine;
+ private String httpServerBase;
+
public CmsRcpApp(String uiName) {
super(uiName);
uid = UUID.randomUUID().toString();
}
+ @SuppressWarnings("rawtypes")
public void initRcpApp() {
+ imageManager = (CmsImageManager) new AcrSwtImageManager();
+
display = Display.getCurrent();
shell = new Shell(display);
shell.setText("Argeo CMS");
return cmsApp;
}
+ @Override
+ public URI toBackendUri(String url) {
+ try {
+ URI u = new URI(url);
+ if (u.getHost() == null) {
+ // TODO make it more robust
+ u = new URI(httpServerBase + url);
+ }
+ return u;
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot convert " + url, e);
+ }
+ }
+
+ public void setHttpServerBase(String httpServerBase) {
+ this.httpServerBase = httpServerBase;
+ }
+
/*
* DEPENDENCY INJECTION
*/
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
import java.nio.file.Path;
import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsDeployment;
import org.argeo.cms.util.OS;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.widgets.Display;
+import com.sun.net.httpserver.HttpsServer;
/** Creates the SWT {@link Display} in a dedicated thread. */
public class CmsRcpDisplayFactory {
private final static String ARGEO_RCP_URL = "argeo.rcp.url";
/** There is only one display in RCP mode */
- private static Display display;
+ private Display display;
private CmsUiThread uiThread;
private boolean shutdown = false;
+ private CmsDeployment cmsDeployment;
+
public void init() {
uiThread = new CmsUiThread();
uiThread.start();
}
}
- public static Display getDisplay() {
+ public Display getDisplay() {
return display;
}
- public static void openCmsApp(CmsApp cmsApp, String uiName, DisposeListener disposeListener) {
- CmsRcpDisplayFactory.getDisplay().syncExec(() -> {
- CmsRcpApp cmsRcpApp = new CmsRcpApp(uiName);
- cmsRcpApp.setCmsApp(cmsApp, null);
- cmsRcpApp.initRcpApp();
- if (disposeListener != null)
- cmsRcpApp.getShell().addDisposeListener(disposeListener);
+ public void openCmsApp(CmsApp cmsApp, String uiName, DisposeListener disposeListener) {
+ cmsDeployment.getHttpServer().thenAccept((httpServer) -> {
+ getDisplay().syncExec(() -> {
+ CmsRcpApp cmsRcpApp = new CmsRcpApp(uiName);
+ cmsRcpApp.setCmsApp(cmsApp, null);
+ if (httpServer != null) {
+ InetSocketAddress addr = httpServer.getAddress();
+ String scheme = "http";
+ if (httpServer instanceof HttpsServer httpsServer) {
+ if (httpsServer.getHttpsConfigurator() != null)
+ scheme = "https";
+ }
+ String httpServerBase = scheme + "://" + addr.getHostString() + ":" + addr.getPort();
+ cmsRcpApp.setHttpServerBase(httpServerBase);
+ }
+ cmsRcpApp.initRcpApp();
+ if (disposeListener != null)
+ cmsRcpApp.getShell().addDisposeListener(disposeListener);
+ });
});
}
public static Path getUrlRunFile() {
return OS.getRunDir().resolve(CmsRcpDisplayFactory.ARGEO_RCP_URL);
}
+
+ public void setCmsDeployment(CmsDeployment cmsDeployment) {
+ this.cmsDeployment = cmsDeployment;
+ }
+
}
private final static Logger logger = System.getLogger(CmsRcpHttpLauncher.class.getName());
private CompletableFuture<HttpServer> httpServer = new CompletableFuture<>();
+ private CmsRcpDisplayFactory cmsRcpDisplayFactory;
+
public void init() {
}
public void handle(HttpExchange exchange) throws IOException {
String path = exchange.getRequestURI().getPath();
String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) : "";
- CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
+ cmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
exchange.sendResponseHeaders(200, -1);
logger.log(Level.DEBUG, "Opened RCP UI " + uiName + " of CMS App /" + contextName);
}
import org.argeo.api.cms.CmsApp;
import org.argeo.cms.dbus.CmsDBus;
+import org.argeo.cms.ui.rcp.CmsRcpDisplayFactory;
public class CmsRcpDBusLauncher {
private CompletableFuture<CmsDBus> cmsDBus = new CompletableFuture<>();
private Map<String, CmsRcpFreeDesktopApplication> apps = new HashMap<>();
+ private CmsRcpDisplayFactory cmsRcpDisplayFactory;
+
public void start() {
}
public void addCmsApp(CmsApp cmsApp, Map<String, String> properties) {
final String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
cmsDBus.thenAcceptAsync((cmsDBus) -> {
- CmsRcpFreeDesktopApplication application = new CmsRcpFreeDesktopApplication(cmsDBus, contextName, cmsApp);
+ CmsRcpFreeDesktopApplication application = new CmsRcpFreeDesktopApplication(cmsRcpDisplayFactory, cmsDBus,
+ contextName, cmsApp);
apps.put(contextName, application);
});
}
this.cmsDBus.complete(cmsDBus);
}
+ public void setCmsRcpDisplayFactory(CmsRcpDisplayFactory cmsRcpDisplayFactory) {
+ this.cmsRcpDisplayFactory = cmsRcpDisplayFactory;
+ }
+
}
private DBusConnection dBusConnection;
- public CmsRcpFreeDesktopApplication(CmsDBus cmsDBus, String contextName, CmsApp cmsApp) {
+ private CmsRcpDisplayFactory cmsRcpDisplayFactory;
+
+ public CmsRcpFreeDesktopApplication(CmsRcpDisplayFactory cmsRcpDisplayFactory, CmsDBus cmsDBus, String contextName,
+ CmsApp cmsApp) {
+ this.cmsRcpDisplayFactory = cmsRcpDisplayFactory;
// TODO find a better prefix and/or make it customisable
this.path = "/org/argeo/cms/" + contextName;
this.cmsApp = cmsApp;
// String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) :
// "";
String uiName = "app";
- CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
+ cmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
}
@Override