From 8c6e16aa43d9523e1ec57a41a06b3ceba7d23fdb Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sun, 24 Jul 2022 11:15:01 +0200 Subject: [PATCH] Introduce CMS event bus and use it in UI. --- .../src/org/argeo/api/cms/CmsContext.java | 10 +- .../src/org/argeo/api/cms/CmsEventBus.java | 12 ++ .../src/org/argeo/api/cms/CmsState.java | 2 + .../cms/websocket/server/EventEndpoint.java | 10 +- .../org/argeo/cms/jetty/CmsJettyServer.java | 79 +++++------- .../org/argeo/cms/jetty/JettyHttpServer.java | 26 +++- org.argeo.cms/OSGI-INF/cmsContext.xml | 1 + org.argeo.cms/OSGI-INF/cmsEventBus.xml | 7 ++ org.argeo.cms/bnd.bnd | 3 +- org.argeo.cms/build.properties | 3 +- .../org/argeo/cms/auth/RemoteAuthUtils.java | 7 +- .../argeo/cms/auth/SingleUserLoginModule.java | 13 +- .../org/argeo/cms/auth/SpnegoLoginModule.java | 6 +- .../cms/internal/runtime/CmsContextImpl.java | 116 ++++-------------- .../cms/internal/runtime/CmsEventBusImpl.java | 105 ++++++++++++++++ .../cms/internal/runtime/CmsStateImpl.java | 39 +++++- .../org/argeo/cms/swt/AbstractSwtCmsView.java | 110 +++++++++++++++++ .../src/org/argeo/cms/swt/auth/CmsLogin.java | 2 +- .../OSGI-INF/cmsWebAppFactory.xml | 2 +- .../src/org/argeo/cms/web/CmsWebApp.java | 15 ++- .../org/argeo/cms/web/CmsWebEntryPoint.java | 83 ++----------- .../argeo/cms/web/osgi/CmsWebAppFactory.java | 11 +- .../src/org/argeo/cms/swt/rcp/cli/CmsCli.java | 2 +- .../OSGI-INF/cmsRcpServletFactory.xml | 3 +- .../src/org/argeo/cms/ui/rcp/CmsRcpApp.java | 88 ++----------- .../cms/ui/rcp/CmsRcpDisplayFactory.java | 7 +- .../cms/ui/rcp/servlet/CmsRcpServlet.java | 8 +- .../ui/rcp/servlet/CmsRcpServletFactory.java | 68 +++++----- 28 files changed, 436 insertions(+), 402 deletions(-) create mode 100644 org.argeo.api.cms/src/org/argeo/api/cms/CmsEventBus.java create mode 100644 org.argeo.cms/OSGI-INF/cmsEventBus.xml create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java create mode 100644 swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java index 64bb4255c..6ad0f512c 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java @@ -2,7 +2,7 @@ package org.argeo.api.cms; import java.util.List; import java.util.Locale; -import java.util.Map; +import java.util.UUID; import javax.security.auth.Subject; @@ -29,11 +29,9 @@ public interface CmsContext { /** Get the CMS session of this subject. */ CmsSession getCmsSession(Subject subject); - CmsState getCmsState(); + CmsEventBus getCmsEventBus(); - void sendEvent(String topic, Map event); + /** A new time based {@link UUID} (v1) using the current time */ + UUID timeUUID(); - void addEventSubscriber(String topic, CmsEventSubscriber eventSubscriber); - - void removeEventSubscriber(String topic, CmsEventSubscriber eventSubscriber); } diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventBus.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventBus.java new file mode 100644 index 000000000..bb8a78206 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventBus.java @@ -0,0 +1,12 @@ +package org.argeo.api.cms; + +import java.util.Map; + +public interface CmsEventBus { + void sendEvent(String topic, Map event); + + void addEventSubscriber(String topic, CmsEventSubscriber eventSubscriber); + + void removeEventSubscriber(String topic, CmsEventSubscriber eventSubscriber); + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java index 9ffba68c1..181e4b9c6 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java @@ -6,6 +6,8 @@ import java.util.UUID; /** A running node process. */ public interface CmsState { + String getHostname(); + Long getAvailableSince(); UUID getUuid(); diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java index a6b2a4df7..c71c862d6 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java @@ -10,7 +10,7 @@ import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; -import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.CmsEventBus; import org.argeo.api.cms.CmsEventSubscriber; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; @@ -20,15 +20,15 @@ public class EventEndpoint implements CmsEventSubscriber { private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext(); private RemoteEndpoint.Basic remote; - private CmsContext cmsContext; + private CmsEventBus cmsEventBus; // private String topic = "cms"; @OnOpen public void onOpen(Session session, @PathParam("topic") String topic) { if (bc != null) { - cmsContext = bc.getService(bc.getServiceReference(CmsContext.class)); - cmsContext.addEventSubscriber(topic, this); + cmsEventBus = bc.getService(bc.getServiceReference(CmsEventBus.class)); + cmsEventBus.addEventSubscriber(topic, this); } remote = session.getBasicRemote(); @@ -36,7 +36,7 @@ public class EventEndpoint implements CmsEventSubscriber { @OnClose public void onClose(@PathParam("topic") String topic) { - cmsContext.removeEventSubscriber(topic, this); + cmsEventBus.removeEventSubscriber(topic, this); } @Override diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java index 4927e6f10..3d4a57b9e 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java @@ -3,24 +3,11 @@ package org.argeo.cms.jetty; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Map; -import javax.servlet.ServletContext; import javax.servlet.ServletException; -import javax.websocket.DeploymentException; -import javax.websocket.server.ServerContainer; -import javax.websocket.server.ServerEndpointConfig; -import com.sun.net.httpserver.Authenticator; -import com.sun.net.httpserver.HttpContext; - -import org.argeo.api.cms.CmsState; -import org.argeo.cms.CmsDeployProperty; -import org.argeo.cms.websocket.server.CmsWebSocketConfigurator; -import org.argeo.cms.websocket.server.TestEndpoint; + import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; -import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator; public class CmsJettyServer extends JettyHttpServer { private static final String CONTEXT_TEMPDIR = "javax.servlet.context.tempdir"; @@ -37,9 +24,7 @@ public class CmsJettyServer extends JettyHttpServer { // WebSocket // private ServerContainer wsServerContainer; - private ServerEndpointConfig.Configurator wsEndpointConfigurator; - - private CmsState cmsState; +// private ServerEndpointConfig.Configurator wsEndpointConfigurator; // private Authenticator defaultAuthenticator; @@ -74,7 +59,7 @@ public class CmsJettyServer extends JettyHttpServer { @Override protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException { addServlets(servletContextHandler); - enableWebSocket(servletContextHandler); +// enableWebSocket(servletContextHandler); } @@ -85,39 +70,31 @@ public class CmsJettyServer extends JettyHttpServer { // return httpContext; // } - protected void enableWebSocket(ServletContextHandler servletContextHandler) { - String webSocketEnabled = getDeployProperty(CmsDeployProperty.WEBSOCKET_ENABLED); - // web socket - if (webSocketEnabled != null && webSocketEnabled.equals(Boolean.toString(true))) { -// JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() { -// -// @Override -// public void accept(ServletContext servletContext, ServerContainer serverContainer) -// throws DeploymentException { -//// wsServerContainer = serverContainer; -// -// CmsWebSocketConfigurator wsEndpointConfigurator = new CmsWebSocketConfigurator(); -// -// ServerEndpointConfig config = ServerEndpointConfig.Builder -// .create(TestEndpoint.class, "/ws/test/events/{topic}").configurator(wsEndpointConfigurator) -// .build(); -// try { -// serverContainer.addEndpoint(config); -// } catch (DeploymentException e) { -// throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e); -// } -// } -// }); - } - } - - protected String getDeployProperty(CmsDeployProperty deployProperty) { - return cmsState.getDeployProperty(deployProperty.getProperty()); - } - - public void setCmsState(CmsState cmsState) { - this.cmsState = cmsState; - } +// protected void enableWebSocket(ServletContextHandler servletContextHandler) { +// String webSocketEnabled = getDeployProperty(CmsDeployProperty.WEBSOCKET_ENABLED); +// // web socket +// if (webSocketEnabled != null && webSocketEnabled.equals(Boolean.toString(true))) { +//// JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() { +//// +//// @Override +//// public void accept(ServletContext servletContext, ServerContainer serverContainer) +//// throws DeploymentException { +////// wsServerContainer = serverContainer; +//// +//// CmsWebSocketConfigurator wsEndpointConfigurator = new CmsWebSocketConfigurator(); +//// +//// ServerEndpointConfig config = ServerEndpointConfig.Builder +//// .create(TestEndpoint.class, "/ws/test/events/{topic}").configurator(wsEndpointConfigurator) +//// .build(); +//// try { +//// serverContainer.addEndpoint(config); +//// } catch (DeploymentException e) { +//// throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e); +//// } +//// } +//// }); +// } +// } // public void setDefaultAuthenticator(Authenticator defaultAuthenticator) { // this.defaultAuthenticator = defaultAuthenticator; diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java index 0eaaadb5f..e414f5f98 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java @@ -10,6 +10,7 @@ import java.util.concurrent.ThreadPoolExecutor; import javax.servlet.ServletException; import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsState; import org.argeo.cms.CmsDeployProperty; import org.argeo.util.http.HttpServerUtils; import org.eclipse.jetty.http.UriCompliance; @@ -41,7 +42,8 @@ public class JettyHttpServer extends HttpsServer { protected ServerConnector httpConnector; protected ServerConnector httpsConnector; - private InetSocketAddress address; + private InetSocketAddress httpAddress; + private InetSocketAddress httpsAddress; private ThreadPoolExecutor executor; @@ -53,6 +55,8 @@ public class JettyHttpServer extends HttpsServer { private boolean started; + private CmsState cmsState; + @Override public void bind(InetSocketAddress addr, int backlog) throws IOException { throw new UnsupportedOperationException(); @@ -182,7 +186,7 @@ public class JettyHttpServer extends HttpsServer { @Override public InetSocketAddress getAddress() { - return address; + return httpAddress; } @Override @@ -195,8 +199,6 @@ public class JettyHttpServer extends HttpsServer { return httpsConfigurator; } - - protected void configureConnectors() { HttpConfiguration httpConfiguration = new HttpConfiguration(); @@ -210,6 +212,10 @@ public class JettyHttpServer extends HttpsServer { // try { if (httpPortStr != null || httpsPortStr != null) { + // TODO deal with hostname resolving taking too much time +// String fallBackHostname = InetAddress.getLocalHost().getHostName(); + String fallBackHostname = cmsState != null ? cmsState.getHostname() : "::1"; + boolean httpEnabled = httpPortStr != null; // props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled); boolean httpsEnabled = httpsPortStr != null; @@ -226,6 +232,8 @@ public class JettyHttpServer extends HttpsServer { httpConnector.setPort(httpPort); httpConnector.setHost(httpHost); httpConnector.setIdleTimeout(DEFAULT_IDLE_TIMEOUT); + + httpAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname, httpPort); } if (httpsEnabled) { @@ -261,6 +269,8 @@ public class JettyHttpServer extends HttpsServer { int httpsPort = Integer.parseInt(httpsPortStr); httpsConnector.setPort(httpsPort); httpsConnector.setHost(httpHost); + + httpsAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname, httpsPort); } } @@ -268,7 +278,8 @@ public class JettyHttpServer extends HttpsServer { } protected String getDeployProperty(CmsDeployProperty deployProperty) { - return System.getProperty(deployProperty.getProperty()); + return cmsState != null ? cmsState.getDeployProperty(deployProperty.getProperty()) + : System.getProperty(deployProperty.getProperty()); } private String httpPortsMsg() { @@ -297,7 +308,10 @@ public class JettyHttpServer extends HttpsServer { } - + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; + } + public boolean isStarted() { return started; } diff --git a/org.argeo.cms/OSGI-INF/cmsContext.xml b/org.argeo.cms/OSGI-INF/cmsContext.xml index dfcf7987e..d2413bd1f 100644 --- a/org.argeo.cms/OSGI-INF/cmsContext.xml +++ b/org.argeo.cms/OSGI-INF/cmsContext.xml @@ -6,6 +6,7 @@ + diff --git a/org.argeo.cms/OSGI-INF/cmsEventBus.xml b/org.argeo.cms/OSGI-INF/cmsEventBus.xml new file mode 100644 index 000000000..6bb67ceed --- /dev/null +++ b/org.argeo.cms/OSGI-INF/cmsEventBus.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index 927a7555a..ddc328c21 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -9,8 +9,9 @@ org.apache.xerces.jaxp;resolution:=optional,\ Service-Component:\ OSGI-INF/cmsOsgiLogger.xml,\ -OSGI-INF/cmsState.xml,\ OSGI-INF/uuidFactory.xml,\ +OSGI-INF/cmsEventBus.xml,\ +OSGI-INF/cmsState.xml,\ OSGI-INF/transactionManager.xml,\ OSGI-INF/cmsUserAdmin.xml,\ OSGI-INF/cmsUserManager.xml,\ diff --git a/org.argeo.cms/build.properties b/org.argeo.cms/build.properties index 960230a18..6ca041a2a 100644 --- a/org.argeo.cms/build.properties +++ b/org.argeo.cms/build.properties @@ -1,6 +1,7 @@ bin.includes = META-INF/,\ .,\ bin/,\ - OSGI-INF/ + OSGI-INF/,\ + OSGI-INF/cmsEventBus.xml source.. = src/ output.. = bin/ diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java index 1f2ea73a7..757bf73bf 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java @@ -14,7 +14,6 @@ import javax.security.auth.login.LoginException; import org.argeo.api.cms.CmsAuth; import org.argeo.api.cms.CmsLog; import org.argeo.api.cms.CmsSession; -import org.argeo.cms.internal.http.CmsAuthenticator; import org.argeo.cms.internal.runtime.CmsContextImpl; import org.argeo.util.http.HttpHeader; import org.ietf.jgss.GSSContext; @@ -148,7 +147,7 @@ public class RemoteAuthUtils { public static int askForWwwAuth(RemoteAuthResponse remoteAuthResponse, String realm, boolean forceBasic) { // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic // realm=\"" + httpAuthRealm + "\""); - if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO + if (hasAcceptorCredentials() && !forceBasic)// SPNEGO remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(), HttpHeader.NEGOTIATE); else remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(), @@ -165,4 +164,8 @@ public class RemoteAuthUtils { return 401; } + private static boolean hasAcceptorCredentials() { + return CmsContextImpl.getCmsContext().getAcceptorCredentials() != null; + } + } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java index 972d6a245..956987d52 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java @@ -14,6 +14,7 @@ import javax.security.auth.spi.LoginModule; import javax.security.auth.x500.X500Principal; import org.argeo.api.cms.CmsLog; +import org.argeo.cms.internal.runtime.CmsContextImpl; import org.argeo.osgi.useradmin.OsUserUtils; import org.argeo.util.directory.ldap.IpaUtils; import org.argeo.util.naming.LdapAttrs; @@ -54,13 +55,7 @@ public class SingleUserLoginModule implements LoginModule { Object username = sharedState.get(CmsAuthUtils.SHARED_STATE_NAME); if (username == null) throw new LoginException("No username available"); - String hostname; - try { - hostname = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - log.warn("Using localhost as hostname", e); - hostname = "localhost"; - } + String hostname = CmsContextImpl.getCmsContext().getCmsState().getHostname(); String baseDn = ("." + hostname).replaceAll("\\.", ",dc="); X500Principal principal = new X500Principal(LdapAttrs.uid + "=" + username + baseDn); authorizationName = principal.getName(); @@ -74,8 +69,8 @@ public class SingleUserLoginModule implements LoginModule { locale = Locale.getDefault(); Authorization authorization = new SingleUserAuthorization(authorizationName); CmsAuthUtils.addAuthorization(subject, authorization); - - // Add standard Java OS login + + // Add standard Java OS login OsUserUtils.loginAsSystemUser(subject); // additional principals (must be after Authorization registration) diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java index dad0dad4b..e24e5b45b 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java @@ -122,7 +122,7 @@ public class SpnegoLoginModule implements LoginModule { private GSSContext checkToken(byte[] authToken) { GSSManager manager = GSSManager.getInstance(); try { - GSSContext gContext = manager.createContext(CmsContextImpl.getAcceptorCredentials()); + GSSContext gContext = manager.createContext(CmsContextImpl.getCmsContext().getAcceptorCredentials()); if (gContext == null) { log.debug("SpnegoUserRealm: failed to establish GSSContext"); @@ -143,8 +143,4 @@ public class SpnegoLoginModule implements LoginModule { } - @Deprecated - public static boolean hasAcceptorCredentials() { - return CmsContextImpl.getAcceptorCredentials() != null; - } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java index af45b74e3..c2d2ccf42 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java @@ -6,19 +6,15 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Flow; -import java.util.concurrent.Flow.Subscription; -import java.util.concurrent.SubmissionPublisher; import javax.security.auth.Subject; import org.argeo.api.cms.CmsContext; import org.argeo.api.cms.CmsDeployment; -import org.argeo.api.cms.CmsEventSubscriber; +import org.argeo.api.cms.CmsEventBus; import org.argeo.api.cms.CmsLog; import org.argeo.api.cms.CmsSession; import org.argeo.api.cms.CmsSessionId; @@ -40,6 +36,7 @@ public class CmsContextImpl implements CmsContext { private CmsDeployment cmsDeployment; private UserAdmin userAdmin; private UuidFactory uuidFactory; + private CmsEventBus cmsEventBus; // private ProvidedRepository contentRepository; // i18n @@ -52,10 +49,6 @@ public class CmsContextImpl implements CmsContext { private Map cmsSessionsByUuid = new HashMap<>(); private Map cmsSessionsByLocalId = new HashMap<>(); - // CMS events - private Map>> topics = new TreeMap<>(); -// private IdentityHashMap> subscriptions = new IdentityHashMap<>(); - public void start() { List codes = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.LOCALE); locales = getLocaleList(codes); @@ -198,6 +191,11 @@ public class CmsContextImpl implements CmsContext { return defaultLocale; } + @Override + public UUID timeUUID() { + return uuidFactory.timeUUID(); + } + @Override public List getLocales() { return locales; @@ -212,11 +210,19 @@ public class CmsContextImpl implements CmsContext { return availableSince != null; } - @Override public CmsState getCmsState() { return cmsState; } + @Override + public CmsEventBus getCmsEventBus() { + return cmsEventBus; + } + + public void setCmsEventBus(CmsEventBus cmsEventBus) { + this.cmsEventBus = cmsEventBus; + } + /* * STATIC */ @@ -226,9 +232,9 @@ public class CmsContextImpl implements CmsContext { } /** Required by SPNEGO login module. */ - public static GSSCredential getAcceptorCredentials() { + public GSSCredential getAcceptorCredentials() { // TODO find a cleaner way - return ((CmsUserAdmin) getInstance().userAdmin).getAcceptorCredentials(); + return ((CmsUserAdmin) userAdmin).getAcceptorCredentials(); } private static void setInstance(CmsContextImpl cmsContextImpl) { @@ -240,12 +246,14 @@ public class CmsContextImpl implements CmsContext { // instance = null; // } // CmsContextImpl.class.notifyAll(); - + if (cmsContextImpl != null) { if (instance.isDone()) throw new IllegalStateException("CMS Context is already set"); instance.complete(cmsContextImpl); } else { + if (!instance.isDone()) + instance.cancel(true); instance = new CompletableFuture(); } } @@ -316,86 +324,4 @@ public class CmsContextImpl implements CmsContext { return cmsSessionsByLocalId.get(localId); } - /* - * CMS Events - */ - public void sendEvent(String topic, Map event) { - SubmissionPublisher> publisher = topics.get(topic); - if (publisher == null) - return; // no one is interested - publisher.submit(event); - } - - public void addEventSubscriber(String topic, CmsEventSubscriber subscriber) { - synchronized (topics) { - if (!topics.containsKey(topic)) - topics.put(topic, new SubmissionPublisher<>()); - } - SubmissionPublisher> publisher = topics.get(topic); - CmsEventFlowSubscriber flowSubscriber = new CmsEventFlowSubscriber(topic, subscriber); - publisher.subscribe(flowSubscriber); - } - - public void removeEventSubscriber(String topic, CmsEventSubscriber subscriber) { - SubmissionPublisher> publisher = topics.get(topic); - if (publisher == null) { - log.error("There should be an event topic " + topic); - return; - } - for (Flow.Subscriber> flowSubscriber : publisher.getSubscribers()) { - if (flowSubscriber instanceof CmsEventFlowSubscriber) - ((CmsEventFlowSubscriber) flowSubscriber).unsubscribe(); - } - synchronized (topics) { - if (!publisher.hasSubscribers()) { - publisher.close(); - topics.remove(topic); - } - } - } - - static class CmsEventFlowSubscriber implements Flow.Subscriber> { - private String topic; - private CmsEventSubscriber eventSubscriber; - - private Subscription subscription; - - public CmsEventFlowSubscriber(String topic, CmsEventSubscriber eventSubscriber) { - this.topic = topic; - this.eventSubscriber = eventSubscriber; - } - - @Override - public void onSubscribe(Subscription subscription) { - this.subscription = subscription; - subscription.request(Long.MAX_VALUE); - } - - @Override - public void onNext(Map item) { - eventSubscriber.onEvent(topic, item); - - } - - @Override - public void onError(Throwable throwable) { - // TODO Auto-generated method stub - - } - - @Override - public void onComplete() { - // TODO Auto-generated method stub - - } - - void unsubscribe() { - if (subscription != null) - subscription.cancel(); - else - throw new IllegalStateException("No subscription to cancel"); - } - - } - } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java new file mode 100644 index 000000000..7fca23c99 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java @@ -0,0 +1,105 @@ +package org.argeo.cms.internal.runtime; + +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.Flow; +import java.util.concurrent.Flow.Subscription; +import java.util.concurrent.SubmissionPublisher; + +import org.argeo.api.cms.CmsEventBus; +import org.argeo.api.cms.CmsEventSubscriber; +import org.argeo.api.cms.CmsLog; + +public class CmsEventBusImpl implements CmsEventBus { + private final CmsLog log = CmsLog.getLog(CmsEventBus.class); + + // CMS events + private Map>> topics = new TreeMap<>(); +// private IdentityHashMap> subscriptions = new IdentityHashMap<>(); + + /* + * CMS Events + */ + @Override + public void sendEvent(String topic, Map event) { + SubmissionPublisher> publisher = topics.get(topic); + if (publisher == null) + return; // no one is interested + publisher.submit(event); + } + + @Override + public void addEventSubscriber(String topic, CmsEventSubscriber subscriber) { + synchronized (topics) { + if (!topics.containsKey(topic)) + topics.put(topic, new SubmissionPublisher<>()); + } + SubmissionPublisher> publisher = topics.get(topic); + CmsEventFlowSubscriber flowSubscriber = new CmsEventFlowSubscriber(topic, subscriber); + publisher.subscribe(flowSubscriber); + } + + @Override + public void removeEventSubscriber(String topic, CmsEventSubscriber subscriber) { + SubmissionPublisher> publisher = topics.get(topic); + if (publisher == null) { + log.error("There should be an event topic " + topic); + return; + } + for (Flow.Subscriber> flowSubscriber : publisher.getSubscribers()) { + if (flowSubscriber instanceof CmsEventFlowSubscriber) + ((CmsEventFlowSubscriber) flowSubscriber).unsubscribe(); + } + synchronized (topics) { + if (!publisher.hasSubscribers()) { + publisher.close(); + topics.remove(topic); + } + } + } + + static class CmsEventFlowSubscriber implements Flow.Subscriber> { + private String topic; + private CmsEventSubscriber eventSubscriber; + + private Subscription subscription; + + public CmsEventFlowSubscriber(String topic, CmsEventSubscriber eventSubscriber) { + this.topic = topic; + this.eventSubscriber = eventSubscriber; + } + + @Override + public void onSubscribe(Subscription subscription) { + this.subscription = subscription; + subscription.request(1); + } + + @Override + public void onNext(Map item) { + eventSubscriber.onEvent(topic, item); + subscription.request(1); + } + + @Override + public void onError(Throwable throwable) { + // TODO Auto-generated method stub + + } + + @Override + public void onComplete() { + // TODO Auto-generated method stub + + } + + void unsubscribe() { + if (subscription != null) + subscription.cancel(); + else + throw new IllegalStateException("No subscription to cancel"); + } + + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java index 902fe793b..c9109c856 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java @@ -2,7 +2,9 @@ package org.argeo.cms.internal.runtime; import java.io.IOException; import java.io.Reader; +import java.net.InetAddress; import java.net.URL; +import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -21,6 +23,11 @@ import java.util.Objects; import java.util.Set; import java.util.StringJoiner; import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.security.auth.login.Configuration; @@ -43,7 +50,7 @@ public class CmsStateImpl implements CmsState { private UUID uuid; // private final boolean cleanState; -// private String hostname; + private String hostname; private UuidFactory uuidFactory; @@ -96,11 +103,27 @@ public class CmsStateImpl implements CmsState { // this.uuid = UUID.fromString(stateUuidStr); this.uuid = uuidFactory.timeUUID(); // this.cleanState = stateUuid.equals(frameworkUuid); -// try { -// this.hostname = InetAddress.getLocalHost().getHostName(); -// } catch (UnknownHostException e) { -// log.error("Cannot set hostname: " + e); -// } + + // hostname + this.hostname = getDeployProperty(CmsDeployProperty.HOST); + // TODO verify we have access to the IP address + if (hostname == null) { + final String LOCALHOST_IP = "::1"; + ForkJoinTask hostnameFJT = ForkJoinPool.commonPool().submit(() -> { + try { + String hostname = InetAddress.getLocalHost().getHostName(); + return hostname; + } catch (UnknownHostException e) { + throw new IllegalStateException("Cannot get local hostname", e); + } + }); + try { + this.hostname = hostnameFJT.get(5, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + this.hostname = LOCALHOST_IP; + log.warn("Could not get local hostname, using " + this.hostname); + } + } availableSince = System.currentTimeMillis(); if (log.isDebugEnabled()) { @@ -373,6 +396,10 @@ public class CmsStateImpl implements CmsState { this.uuidFactory = uuidFactory; } + public String getHostname() { + return hostname; + } + /** * Called before node initialisation, in order populate OSGi instance are with * some files (typically LDIF, etc). diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java new file mode 100644 index 000000000..205980303 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java @@ -0,0 +1,110 @@ +package org.argeo.cms.swt; + +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; + +import org.argeo.api.cms.CmsEventBus; +import org.argeo.api.cms.ux.CmsImageManager; +import org.argeo.api.cms.ux.CmsUi; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.api.cms.ux.UxContext; +import org.argeo.cms.auth.CurrentUser; +import org.eclipse.swt.widgets.Display; + +public abstract class AbstractSwtCmsView implements CmsView { + protected final String uiName; + + protected LoginContext loginContext; + protected String state; + protected Throwable exception; + protected UxContext uxContext; + protected CmsImageManager imageManager; + + protected Display display; + protected CmsUi ui; + + protected String uid; + + public AbstractSwtCmsView(String uiName) { + this.uiName = uiName; + } + + public abstract CmsEventBus getCmsEventBus(); + + @Override + public void sendEvent(String topic, Map properties) { + if (properties == null) + properties = new HashMap<>(); + if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid)) + throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid (" + + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid); + properties.put(CMS_VIEW_UID_PROPERTY, uid); + getCmsEventBus().sendEvent(topic, properties); + } + + public T doAs(PrivilegedAction action) { + try { + CompletableFuture result = new CompletableFuture<>(); + Runnable toDo = () -> { + T res = Subject.doAs(getSubject(), action); + result.complete(res); + }; + if (Thread.currentThread() == display.getThread()) + toDo.run(); + else + display.syncExec(toDo); + return result.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException("Cannot execute action ins CMS view " + uid, e); + } + } + + @Override + public UxContext getUxContext() { + return uxContext; + } + + @Override + public String getUid() { + return uid; + } + + @Override + public CmsImageManager getImageManager() { + return imageManager; + } + + @Override + public boolean isAnonymous() { + return CurrentUser.isAnonymous(getSubject()); + } + + protected Subject getSubject() { + return loginContext.getSubject(); + } + + @Override + public Object getData(String key) { + if (ui != null) { + return ui.getData(key); + } else { + throw new IllegalStateException("UI is not initialized"); + } + } + + @Override + public void setData(String key, Object value) { + if (ui != null) { + ui.setData(key, value); + } else { + throw new IllegalStateException("UI is not initialized"); + } + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java index 4af0c6c1a..b713c19fe 100644 --- a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java @@ -279,7 +279,7 @@ public class CmsLogin implements CmsStyles, CallbackHandler { loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this); loginContext.login(); cmsView.authChange(loginContext); - cmsContext.sendEvent("cms", Collections.singletonMap("msg", "New login")); + cmsContext.getCmsEventBus().sendEvent("cms", Collections.singletonMap("msg", "New login")); return true; } catch (LoginException e) { if (log.isTraceEnabled()) diff --git a/swt/rap/org.argeo.cms.swt.rap/OSGI-INF/cmsWebAppFactory.xml b/swt/rap/org.argeo.cms.swt.rap/OSGI-INF/cmsWebAppFactory.xml index aa7e0adca..f5edebcda 100644 --- a/swt/rap/org.argeo.cms.swt.rap/OSGI-INF/cmsWebAppFactory.xml +++ b/swt/rap/org.argeo.cms.swt.rap/OSGI-INF/cmsWebAppFactory.xml @@ -2,5 +2,5 @@ - + diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java index fec9e2485..31555d168 100644 --- a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java @@ -7,6 +7,7 @@ import java.util.Set; import org.argeo.api.cms.CmsApp; import org.argeo.api.cms.CmsAppListener; +import org.argeo.api.cms.CmsEventBus; import org.argeo.api.cms.CmsLog; import org.argeo.api.cms.ux.CmsTheme; import org.argeo.api.cms.ux.CmsView; @@ -21,7 +22,6 @@ import org.eclipse.rap.rwt.client.WebClient; import org.eclipse.swt.widgets.Display; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; -import org.osgi.service.event.EventAdmin; /** An RWT web app integrating with a {@link CmsApp}. */ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, CmsAppListener { @@ -29,8 +29,8 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm private BundleContext bundleContext; private CmsApp cmsApp; -// private String cmsAppId; - private EventAdmin eventAdmin; + + private CmsEventBus cmsEventBus; private ServiceRegistration rwtAppReg; @@ -103,7 +103,6 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm String entryPointName = !uiName.equals("") ? "/" + uiName : "/"; application.addEntryPoint(entryPointName, () -> { CmsWebEntryPoint entryPoint = new CmsWebEntryPoint(this, uiName); - entryPoint.setEventAdmin(eventAdmin); return entryPoint; }, properties); if (log.isDebugEnabled()) @@ -156,8 +155,12 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm } } - public void setEventAdmin(EventAdmin eventAdmin) { - this.eventAdmin = eventAdmin; + public void setCmsEventBus(CmsEventBus cmsEventBus) { + this.cmsEventBus = cmsEventBus; + } + + public CmsEventBus getCmsEventBus() { + return cmsEventBus; } public void setContextName(String contextName) { diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java index 477a6569d..3c894d158 100644 --- a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java @@ -3,9 +3,7 @@ package org.argeo.cms.web; import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext; import java.security.PrivilegedAction; -import java.util.HashMap; import java.util.Locale; -import java.util.Map; import java.util.UUID; import javax.security.auth.Subject; @@ -14,17 +12,17 @@ import javax.security.auth.login.LoginException; import org.argeo.api.cms.CmsApp; import org.argeo.api.cms.CmsAuth; +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.CmsUi; import org.argeo.api.cms.ux.CmsView; -import org.argeo.api.cms.ux.UxContext; import org.argeo.cms.LocaleUtils; import org.argeo.cms.auth.CurrentUser; import org.argeo.cms.auth.RemoteAuthCallbackHandler; import org.argeo.cms.servlet.ServletHttpRequest; import org.argeo.cms.servlet.ServletHttpResponse; +import org.argeo.cms.swt.AbstractSwtCmsView; import org.argeo.cms.swt.CmsSwtUtils; import org.argeo.cms.swt.SimpleSwtUxContext; import org.argeo.cms.swt.acr.AcrSwtImageManager; @@ -41,30 +39,15 @@ import org.eclipse.swt.SWTError; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; -import org.osgi.service.event.Event; -import org.osgi.service.event.EventAdmin; +import org.eclipse.swt.widgets.Widget; /** The {@link CmsView} for a {@link CmsWebApp}. */ @SuppressWarnings("restriction") -public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationListener { +public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint, CmsView, BrowserNavigationListener { private static final long serialVersionUID = 7733510691684570402L; private final static CmsLog log = CmsLog.getLog(CmsWebEntryPoint.class); - private EventAdmin eventAdmin; - private final CmsWebApp cmsWebApp; - private final String uiName; - - private LoginContext loginContext; - private String state; - private Throwable exception; - private UxContext uxContext; - private CmsImageManager imageManager; - - private Display display; - private CmsUi ui; - - private String uid; // Client services // private final JavaScriptExecutor jsExecutor; @@ -74,10 +57,10 @@ public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationL private boolean multipleShells = false; public CmsWebEntryPoint(CmsWebApp cmsWebApp, String uiName) { + super(uiName); assert cmsWebApp != null; assert uiName != null; this.cmsWebApp = cmsWebApp; - this.uiName = uiName; uid = UUID.randomUUID().toString(); // Initial login @@ -136,19 +119,6 @@ public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationL }); } - protected Subject getSubject() { - return loginContext.getSubject(); - } - - public T doAs(PrivilegedAction action) { - return Subject.doAs(getSubject(), action); - } - - @Override - public boolean isAnonymous() { - return CurrentUser.isAnonymous(getSubject()); - } - @Override public synchronized void logout() { if (loginContext == null) @@ -220,16 +190,6 @@ public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationL return null; } - @Override - public UxContext getUxContext() { - return uxContext; - } - - @Override - public String getUid() { - return uid; - } - @Override public void navigateTo(String state) { exception = null; @@ -251,14 +211,8 @@ public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationL } @Override - public void sendEvent(String topic, Map properties) { - if (properties == null) - properties = new HashMap<>(); - if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid)) - throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid (" - + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid); - properties.put(CMS_VIEW_UID_PROPERTY, uid); - eventAdmin.sendEvent(new Event(topic, properties)); + public CmsEventBus getCmsEventBus() { + return cmsWebApp.getCmsEventBus(); } @Override @@ -274,24 +228,6 @@ public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationL return cmsSession; } - @Override - public Object getData(String key) { - if (ui != null) { - return ui.getData(key); - } else { - throw new IllegalStateException("UI is not initialized"); - } - } - - @Override - public void setData(String key, Object value) { - if (ui != null) { - ui.setData(key, value); - } else { - throw new IllegalStateException("UI is not initialized"); - } - } - /* * EntryPoint IMPLEMENTATION */ @@ -360,9 +296,4 @@ public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationL } return shell; } - - public void setEventAdmin(EventAdmin eventAdmin) { - this.eventAdmin = eventAdmin; - } - } diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java index 19b9fe80d..83a83e2ad 100644 --- a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java @@ -6,17 +6,17 @@ import java.util.Hashtable; import java.util.Map; import org.argeo.api.cms.CmsApp; +import org.argeo.api.cms.CmsEventBus; 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 CmsEventBus cmsEventBus; private Map registrations = Collections.synchronizedMap(new HashMap<>()); @@ -24,7 +24,7 @@ public class CmsWebAppFactory { String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY); if (contextName != null) { CmsWebApp cmsWebApp = new CmsWebApp(); - cmsWebApp.setEventAdmin(eventAdmin); + cmsWebApp.setCmsEventBus(cmsEventBus); cmsWebApp.setCmsApp(cmsApp); Hashtable serviceProperties = new Hashtable<>(); if (!contextName.equals("")) @@ -47,8 +47,9 @@ public class CmsWebAppFactory { } } - public void setEventAdmin(EventAdmin eventAdmin) { - this.eventAdmin = eventAdmin; + public void setCmsEventBus(CmsEventBus cmsEventBus) { + this.cmsEventBus = cmsEventBus; } + } diff --git a/swt/rcp/org.argeo.cms.swt.rcp.cli/src/org/argeo/cms/swt/rcp/cli/CmsCli.java b/swt/rcp/org.argeo.cms.swt.rcp.cli/src/org/argeo/cms/swt/rcp/cli/CmsCli.java index 187f5b03e..3fe2935af 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp.cli/src/org/argeo/cms/swt/rcp/cli/CmsCli.java +++ b/swt/rcp/org.argeo.cms.swt.rcp.cli/src/org/argeo/cms/swt/rcp/cli/CmsCli.java @@ -82,7 +82,7 @@ public class CmsCli extends CommandsCli { protected void postActivation(ComponentRegister register) { if (ui) { Component cmsAppC = register.find(CmsUserApp.class, null).first(); - CmsRcpDisplayFactory.openCmsApp(null, cmsAppC.get(), "data", (e) -> { + CmsRcpDisplayFactory.openCmsApp(cmsAppC.get(), "data", (e) -> { // asynchronous in order to avoid deadlock in UI thread ForkJoinPool.commonPool().execute(() -> stop()); }); diff --git a/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpServletFactory.xml b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpServletFactory.xml index a1f0b3a2a..fe0bc64db 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpServletFactory.xml +++ b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpServletFactory.xml @@ -2,6 +2,5 @@ - - + diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java index e25a9f711..3cf243851 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java @@ -3,7 +3,6 @@ package org.argeo.cms.ui.rcp; import java.io.IOException; import java.io.InputStream; import java.security.PrivilegedAction; -import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -13,13 +12,12 @@ import javax.security.auth.login.LoginException; import org.argeo.api.cms.CmsApp; import org.argeo.api.cms.CmsAuth; +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.CmsUi; import org.argeo.api.cms.ux.CmsView; -import org.argeo.api.cms.ux.UxContext; +import org.argeo.cms.swt.AbstractSwtCmsView; import org.argeo.cms.swt.CmsSwtUtils; import org.eclipse.e4.ui.css.core.engine.CSSEngine; import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler; @@ -27,39 +25,24 @@ import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; -import org.osgi.service.event.Event; -import org.osgi.service.event.EventAdmin; /** Runs a {@link CmsApp} as an SWT desktop application. */ @SuppressWarnings("restriction") -public class CmsRcpApp implements CmsView { +public class CmsRcpApp extends AbstractSwtCmsView implements CmsView { private final static CmsLog log = CmsLog.getLog(CmsRcpApp.class); - // private BundleContext bundleContext = - // FrameworkUtil.getBundle(CmsRcpApp.class).getBundleContext(); - private Shell shell; private CmsApp cmsApp; - // CMS View - private String uid; - private LoginContext loginContext; - - private EventAdmin eventAdmin; - private CSSEngine cssEngine; - private CmsUi ui; - // TODO make it configurable - private String uiName = "desktop"; - public CmsRcpApp(String uiName) { + super(uiName); uid = UUID.randomUUID().toString(); - this.uiName = uiName; } public void initRcpApp() { - Display display = Display.getCurrent(); + display = Display.getCurrent(); shell = new Shell(display); shell.setText("Argeo CMS"); Composite parent = shell; @@ -115,16 +98,6 @@ public class CmsRcpApp implements CmsView { * CMS VIEW */ - @Override - public String getUid() { - return uid; - } - - @Override - public UxContext getUxContext() { - throw new UnsupportedOperationException(); - } - @Override public void navigateTo(String state) { throw new UnsupportedOperationException(); @@ -149,35 +122,12 @@ public class CmsRcpApp implements CmsView { log.error("Unexpected exception in CMS RCP", e); } - @Override - public CmsImageManager getImageManager() { - throw new UnsupportedOperationException(); - } - @Override public CmsSession getCmsSession() { CmsSession cmsSession = cmsApp.getCmsContext().getCmsSession(getSubject()); return cmsSession; } - @Override - public Object getData(String key) { - if (ui != null) { - return ui.getData(key); - } else { - throw new IllegalStateException("UI is not initialized"); - } - } - - @Override - public void setData(String key, Object value) { - if (ui != null) { - ui.setData(key, value); - } else { - throw new IllegalStateException("UI is not initialized"); - } - } - @Override public boolean isAnonymous() { return false; @@ -189,29 +139,15 @@ public class CmsRcpApp implements CmsView { cssEngine.applyStyles(node, true); } - @Override - public void sendEvent(String topic, Map properties) { - if (properties == null) - properties = new HashMap<>(); - if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid)) - throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid (" - + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid); - properties.put(CMS_VIEW_UID_PROPERTY, uid); - eventAdmin.sendEvent(new Event(topic, properties)); - } - - public T doAs(PrivilegedAction action) { - return Subject.doAs(getSubject(), action); - } - - protected Subject getSubject() { - return loginContext.getSubject(); - } - public Shell getShell() { return shell; } + @Override + public CmsEventBus getCmsEventBus() { + return cmsApp.getCmsContext().getCmsEventBus(); + } + /* * DEPENDENCY INJECTION */ @@ -223,8 +159,4 @@ public class CmsRcpApp implements CmsView { this.cmsApp = null; } - public void setEventAdmin(EventAdmin eventAdmin) { - this.eventAdmin = eventAdmin; - } - } diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java index ec471c021..950accece 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java @@ -4,9 +4,8 @@ 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; import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.widgets.Display; /** Creates the SWT {@link Display} in a dedicated thread. */ public class CmsRcpDisplayFactory { @@ -72,11 +71,9 @@ public class CmsRcpDisplayFactory { return display; } - public static void openCmsApp(EventAdmin eventAdmin, CmsApp cmsApp, String uiName, - DisposeListener disposeListener) { + public static void openCmsApp(CmsApp cmsApp, String uiName, DisposeListener disposeListener) { CmsRcpDisplayFactory.getDisplay().syncExec(() -> { CmsRcpApp cmsRcpApp = new CmsRcpApp(uiName); - cmsRcpApp.setEventAdmin(eventAdmin); cmsRcpApp.setCmsApp(cmsApp, null); cmsRcpApp.initRcpApp(); if (disposeListener != null) diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServlet.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServlet.java index f8aecd39b..8579527c1 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServlet.java +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServlet.java @@ -12,7 +12,6 @@ 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 { @@ -20,20 +19,17 @@ public class CmsRcpServlet extends HttpServlet { 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); + public CmsRcpServlet(CmsApp cmsApp) { 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 != null ? path.substring(path.lastIndexOf('/') + 1) : ""; - CmsRcpDisplayFactory.openCmsApp(eventAdmin, cmsApp, uiName, null); + CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null); logger.log(Level.DEBUG, "Opened RCP UI " + uiName + " of CMS App " + req.getServletPath()); } diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServletFactory.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServletFactory.java index 7c24f87d9..09b1e41b4 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServletFactory.java +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServletFactory.java @@ -1,5 +1,7 @@ package org.argeo.cms.ui.rcp.servlet; +import static java.lang.System.Logger.Level.DEBUG; + import java.io.IOException; import java.lang.System.Logger; import java.lang.System.Logger.Level; @@ -9,31 +11,22 @@ import java.net.URI; 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; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; /** 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 = new CompletableFuture<>(); - - private Map> registrations = Collections.synchronizedMap(new HashMap<>()); + private HttpServer httpServer; +// private BundleContext bundleContext = FrameworkUtil.getBundle(CmsRcpServletFactory.class).getBundleContext(); +// +// private Map> registrations = Collections.synchronizedMap(new HashMap<>()); public void init() { @@ -51,33 +44,40 @@ public class CmsRcpServletFactory { } public void addCmsApp(CmsApp cmsApp, Map properties) { - String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY); + final String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY); if (contextName != null) { - eventAdmin.thenAccept((eventAdmin) -> { - CmsRcpServlet servlet = new CmsRcpServlet(eventAdmin, cmsApp); - Hashtable serviceProperties = new Hashtable<>(); - serviceProperties.put("osgi.http.whiteboard.servlet.pattern", "/" + contextName + "/*"); - ServiceRegistration sr = bundleContext.registerService(Servlet.class, servlet, - serviceProperties); - registrations.put(contextName, sr); + httpServer.createContext("/" + contextName, new HttpHandler() { + + @Override + 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); + exchange.sendResponseHeaders(200, -1); + logger.log(Level.DEBUG, "Opened RCP UI " + uiName + " of CMS App /" + contextName); + } }); + logger.log(Level.DEBUG, "Registered RCP CMS APP /" + contextName); +// CmsRcpServlet servlet = new CmsRcpServlet(cmsApp); +// Hashtable serviceProperties = new Hashtable<>(); +// serviceProperties.put("osgi.http.whiteboard.servlet.pattern", "/" + contextName + "/*"); +// ServiceRegistration sr = bundleContext.registerService(Servlet.class, servlet, serviceProperties); +// registrations.put(contextName, sr); } } public void removeCmsApp(CmsApp cmsApp, Map properties) { String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY); if (contextName != null) { - ServiceRegistration sr = registrations.get(contextName); - sr.unregister(); + httpServer.removeContext("/" + contextName); +// ServiceRegistration sr = registrations.get(contextName); +// sr.unregister(); } } - public void setEventAdmin(EventAdmin eventAdmin) { - this.eventAdmin.complete(eventAdmin); - } - - public void setHttpService(HttpService httpService, Map properties) { - Integer httpPort = Integer.parseInt(properties.get("http.port").toString()); + public void setHttpServer(HttpServer httpServer) { + this.httpServer = httpServer; + Integer httpPort = httpServer.getAddress().getPort(); String baseUrl = "http://localhost:" + httpPort + "/"; Path runFile = CmsRcpDisplayFactory.getUrlRunFile(); try { @@ -99,7 +99,7 @@ public class CmsRcpServletFactory { } catch (IOException e) { throw new RuntimeException("Cannot write run file to " + runFile, e); } - logger.log(Level.DEBUG, "RCP available under " + baseUrl + ", written to " + runFile); + logger.log(DEBUG, "RCP available under " + baseUrl + ", written to " + runFile); } protected boolean isPortAvailable(int port) { -- 2.30.2