Improve Jetty integration in order to support consistent HTTP sessions.
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 31 Oct 2022 08:46:44 +0000 (09:46 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 31 Oct 2022 08:46:44 +0000 (09:46 +0100)
23 files changed:
org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCommands.java
org.argeo.cms.ee/OSGI-INF/statusHandler.xml
org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java
org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PingEndpoint.java
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java
org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml
org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java
org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java
org.argeo.cms/src/org/argeo/cms/client/WebSocketPing.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/client/WsPing.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java

index ae2b44174044da09e09e2291a730788cef8ddb25..6f2db4ff22db5e495f5374f8cc162af1edab80e2 100644 (file)
@@ -4,16 +4,22 @@ import java.net.URI;
 import java.util.List;
 
 import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
 import org.argeo.api.cli.CommandArgsException;
 import org.argeo.api.cli.CommandsCli;
 import org.argeo.api.cli.DescribedCommand;
-import org.argeo.cms.client.WsPing;
+import org.argeo.cms.client.WebSocketEventClient;
+import org.argeo.cms.client.WebSocketPing;
 
 public class CmsCommands extends CommandsCli {
+       final static Option connectOption = Option.builder().option("c").longOpt("connect").desc("server to connect to")
+                       .hasArg(true).build();
 
        public CmsCommands(String commandName) {
                super(commandName);
                addCommand("ping", new Ping());
+               addCommand("event", new Events());
        }
 
        @Override
@@ -22,6 +28,46 @@ public class CmsCommands extends CommandsCli {
        }
 
        class Ping implements DescribedCommand<Void> {
+               @Override
+               public Options getOptions() {
+                       Options options = new Options();
+                       options.addOption(connectOption);
+                       return options;
+               }
+
+               @Override
+               public Void apply(List<String> t) {
+                       CommandLine line = toCommandLine(t);
+                       String uriArg = line.getOptionValue(connectOption);
+                       // TODO make it more robust (trailing /, etc.)
+                       URI uri = URI.create(uriArg);
+                       if ("".equals(uri.getPath())) {
+                               uri = URI.create(uri.toString() + "/cms/status/ping");
+                       }
+                       new WebSocketPing(uri).run();
+                       return null;
+               }
+
+               @Override
+               public String getUsage() {
+                       return "[ws|wss]://host:port/";
+               }
+
+               @Override
+               public String getDescription() {
+                       return "Test whether an Argeo CMS is available, without auhtentication";
+               }
+
+       }
+
+       class Events implements DescribedCommand<Void> {
+
+               @Override
+               public Options getOptions() {
+                       Options options = new Options();
+                       options.addOption(connectOption);
+                       return options;
+               }
 
                @Override
                public Void apply(List<String> t) {
@@ -30,24 +76,26 @@ public class CmsCommands extends CommandsCli {
                        if (remaining.size() == 0) {
                                throw new CommandArgsException("There must be at least one argument");
                        }
-                       String uriArg = remaining.get(0);
+                       String topic = remaining.get(0);
+
+                       String uriArg = line.getOptionValue(connectOption);
                        // TODO make it more robust (trailing /, etc.)
                        URI uri = URI.create(uriArg);
                        if ("".equals(uri.getPath())) {
-                               uri = URI.create(uri.toString() + "/cms/status/ping");
+                               uri = URI.create(uri.toString() + "/cms/status/event/" + topic);
                        }
-                       new WsPing(uri).run();
+                       new WebSocketEventClient(uri).run();
                        return null;
                }
 
                @Override
                public String getUsage() {
-                       return "[ws|wss]://host:port/";
+                       return "TOPIC";
                }
 
                @Override
                public String getDescription() {
-                       return "Test whether an Argeo CMS is available, without auhtentication";
+                       return "Listen to events on a topic";
                }
 
        }
index 37e6c5fbaf1b1622a06b07c3eadd027621abb4a2..b6e9bfd059c8985c1a696e9814b1564ebc7ee05e 100644 (file)
@@ -4,6 +4,6 @@
    <service>
       <provide interface="com.sun.net.httpserver.HttpHandler"/>
    </service>
-   <property name="context.path" type="String" value="/cms/status"/>
+   <property name="context.path" type="String" value="/cms/status/"/>
    <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
 </scr:component>
index b2f73944985e23210f267475f58193683b5829ad..63d59a88d0510cf9565ccac177bb1608e96ca68e 100644 (file)
@@ -15,6 +15,10 @@ import com.sun.net.httpserver.HttpContext;
 import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpPrincipal;
 
+/**
+ * An {@link HttpServlet} which integrates an {@link HttpContext} and its
+ * {@link Authenticator} in a servlet container.
+ */
 public class HttpContextServlet extends HttpServlet {
        private static final long serialVersionUID = 2321612280413662738L;
 
index 5a29fbe8401ece3e9c209239b8eeb27fd1428251..f5e9c03945f4877c62f36369d264d75ed5bd1aea 100644 (file)
@@ -18,7 +18,8 @@ import com.sun.net.httpserver.HttpContext;
 import com.sun.net.httpserver.HttpPrincipal;
 import com.sun.net.httpserver.HttpsExchange;
 
-public class ServletHttpExchange extends HttpsExchange {
+/** Integrates {@link HttpsExchange} in a servlet container. */
+class ServletHttpExchange extends HttpsExchange {
        private final HttpContext httpContext;
        private final HttpServletRequest httpServletRequest;
        private final HttpServletResponse httpServletResponse;
index c71c862d65c1cf195a8d0a7399a1ecfbd9a33f43..defc59efcadf1e49926f373fe97781eecdf4c89e 100644 (file)
@@ -1,9 +1,11 @@
 package org.argeo.cms.websocket.server;
 
 import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
 import java.util.Map;
 
 import javax.websocket.OnClose;
+import javax.websocket.OnError;
 import javax.websocket.OnOpen;
 import javax.websocket.RemoteEndpoint;
 import javax.websocket.Session;
@@ -12,11 +14,13 @@ import javax.websocket.server.ServerEndpoint;
 
 import org.argeo.api.cms.CmsEventBus;
 import org.argeo.api.cms.CmsEventSubscriber;
+import org.argeo.api.cms.CmsLog;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 
-@ServerEndpoint(value = "/event/{topic}", configurator = CmsWebSocketConfigurator.class)
+@ServerEndpoint(value = "/cms/status/event/{topic}", configurator = CmsWebSocketConfigurator.class)
 public class EventEndpoint implements CmsEventSubscriber {
+       private final static CmsLog log = CmsLog.getLog(EventEndpoint.class);
        private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext();
 
        private RemoteEndpoint.Basic remote;
@@ -47,4 +51,13 @@ public class EventEndpoint implements CmsEventSubscriber {
                        throw new IllegalStateException(e);
                }
        }
+
+       @OnError
+       public void onError(Throwable e) {
+               if (e instanceof ClosedChannelException) {
+                       // ignore, as it probably means ping was closed on the other side
+                       return;
+               }
+               log.error("Cannot process ping", e);
+       }
 }
index 5ae491acfe15227b4337afe66f0a883c021582ea..dcbce67b17f96bf754061e15e13592e05b1bf7a7 100644 (file)
@@ -7,7 +7,7 @@ import javax.websocket.server.ServerEndpoint;
 
 import org.argeo.api.cms.CmsLog;
 
-@ServerEndpoint(value = "/ping", configurator = PublicWebSocketConfigurator.class)
+@ServerEndpoint(value = "/cms/status/ping", configurator = PublicWebSocketConfigurator.class)
 public class PingEndpoint {
        private final static CmsLog log = CmsLog.getLog(PingEndpoint.class);
 
index 0575726d3615681d5f090e9138078a9abc2862b7..cc608136b47c0ac965fd3a097266862f9bf6e315 100644 (file)
@@ -31,7 +31,7 @@ import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 /** Provides WebSocket access. */
-@ServerEndpoint(value = "/test/{topic}", configurator = CmsWebSocketConfigurator.class)
+@ServerEndpoint(value = "/cms/status/test/{topic}", configurator = CmsWebSocketConfigurator.class)
 public class TestEndpoint implements EventHandler {
        private final static CmsLog log = CmsLog.getLog(TestEndpoint.class);
 
index 1b1b429615134f522c4a774b2b9f3dc69ce25365..e6595a05e9d8379f9f2a25d46a85fd83dbcb3272 100644 (file)
@@ -21,6 +21,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.osgi.framework.Constants;
 
+/** A {@link CmsJettyServer} integrating with Equinox HTTP framework. */
 public class EquinoxJettyServer extends CmsJettyServer {
        private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader";
 
@@ -31,8 +32,6 @@ public class EquinoxJettyServer extends CmsJettyServer {
                holder.setInitParameter(Constants.SERVICE_VENDOR, "Eclipse.org"); //$NON-NLS-1$
                holder.setInitParameter(Constants.SERVICE_DESCRIPTION, "Equinox Jetty-based Http Service"); //$NON-NLS-1$
 
-               // holder.setInitParameter(JettyConstants.CONTEXT_PATH,
-               // httpContext.getContextPath());
                rootContextHandler.addServlet(holder, "/*");
 
                // post-start
index 90a800f7eff894cbfde502d956da8e6aadec77b0..b0b348d9ccfec48a514b93042cd20b2fc2f87757 100644 (file)
@@ -3,11 +3,17 @@ package org.argeo.cms.jetty;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.concurrent.CompletableFuture;
 
+import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
 
 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;
 
 /** A {@link JettyHttpServer} which is compatible with Equinox servlets. */
 public class CmsJettyServer extends JettyHttpServer {
@@ -16,6 +22,8 @@ public class CmsJettyServer extends JettyHttpServer {
        private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader";
        private Path tempDir;
 
+       private CompletableFuture<ServerContainer> serverContainer = new CompletableFuture<>();
+
        protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException {
        }
 
@@ -29,6 +37,7 @@ public class CmsJettyServer extends JettyHttpServer {
                super.start();
        }
 
+       @Override
        protected ServletContextHandler createRootContextHandler() {
                ServletContextHandler servletContextHandler = new ServletContextHandler();
                servletContextHandler.setAttribute(INTERNAL_CONTEXT_CLASSLOADER,
@@ -41,11 +50,30 @@ public class CmsJettyServer extends JettyHttpServer {
                handler.setMaxInactiveInterval(-1);
                servletContextHandler.setSessionHandler(handler);
 
+               JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
+
+                       @Override
+                       public void accept(ServletContext servletContext, ServerContainer serverContainer)
+                                       throws DeploymentException {
+                               CmsJettyServer.this.serverContainer.complete(serverContainer);
+                       }
+               });
+
                return servletContextHandler;
        }
 
+       @Override
+       protected ServerContainer getRootServerContainer() {
+               return serverContainer.join();
+       }
+
        @Override
        protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException {
                addServlets(servletContextHandler);
        }
+
+       /*
+        * WEB SOCKET
+        */
+
 }
diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java
new file mode 100644 (file)
index 0000000..1e64fe0
--- /dev/null
@@ -0,0 +1,64 @@
+package org.argeo.cms.jetty;
+
+import java.util.AbstractMap;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+/**
+ * A {@link Map} implementation wrapping the attributes of a Jetty
+ * {@link ContextHandler}.
+ */
+class ContextHandlerAttributes extends AbstractMap<String, Object> {
+       private ContextHandler contextHandler;
+
+       public ContextHandlerAttributes(ContextHandler contextHandler) {
+               super();
+               this.contextHandler = contextHandler;
+       }
+
+       @Override
+       public Set<Entry<String, Object>> entrySet() {
+               Set<Entry<String, Object>> entries = new HashSet<>();
+               for (Enumeration<String> keys = contextHandler.getAttributeNames(); keys.hasMoreElements();) {
+                       entries.add(new ContextAttributeEntry(keys.nextElement()));
+               }
+               return entries;
+       }
+
+       @Override
+       public Object put(String key, Object value) {
+               Object previousValue = get(key);
+               contextHandler.setAttribute(key, value);
+               return previousValue;
+       }
+
+       private class ContextAttributeEntry implements Map.Entry<String, Object> {
+               private final String key;
+
+               public ContextAttributeEntry(String key) {
+                       this.key = key;
+               }
+
+               @Override
+               public String getKey() {
+                       return key;
+               }
+
+               @Override
+               public Object getValue() {
+                       return contextHandler.getAttribute(key);
+               }
+
+               @Override
+               public Object setValue(Object value) {
+                       Object previousValue = getValue();
+                       contextHandler.setAttribute(key, value);
+                       return previousValue;
+               }
+
+       }
+}
diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java
new file mode 100644 (file)
index 0000000..d6037ba
--- /dev/null
@@ -0,0 +1,84 @@
+package org.argeo.cms.jetty;
+
+import java.util.AbstractMap;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+import org.argeo.cms.servlet.httpserver.HttpContextServlet;
+import org.argeo.cms.websocket.server.WebsocketEndpoints;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * An @{HttpContext} implementation based on a Jetty
+ * {@link ServletContextHandler}.
+ */
+class ContextHandlerHttpContext extends JettyHttpContext {
+       private final ServletContextHandler servletContextHandler;
+       private final ContextHandlerAttributes attributes;
+
+       public ContextHandlerHttpContext(JettyHttpServer httpServer, String path) {
+               super(httpServer, path);
+
+               // Jetty context handler
+               this.servletContextHandler = new ServletContextHandler();
+               servletContextHandler.setContextPath(path);
+               HttpContextServlet servlet = new HttpContextServlet(this);
+               servletContextHandler.addServlet(new ServletHolder(servlet), "/*");
+               SessionHandler sessionHandler = new SessionHandler();
+               // FIXME find a better default
+               sessionHandler.setMaxInactiveInterval(-1);
+               servletContextHandler.setSessionHandler(sessionHandler);
+
+               attributes = new ContextHandlerAttributes(servletContextHandler);
+       }
+
+       @Override
+       public void setHandler(HttpHandler handler) {
+               super.setHandler(handler);
+
+               // web socket
+               if (handler instanceof WebsocketEndpoints) {
+                       JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
+
+                               @Override
+                               public void accept(ServletContext servletContext, ServerContainer serverContainer)
+                                               throws DeploymentException {
+                                       for (Class<?> clss : ((WebsocketEndpoints) handler).getEndPoints()) {
+                                               serverContainer.addEndpoint(clss);
+                                       }
+                               }
+                       });
+               }
+
+               if (getJettyHttpServer().isStarted())
+                       try {
+                               servletContextHandler.start();
+                       } catch (Exception e) {
+                               throw new IllegalStateException("Cannot start context handler", e);
+                       }
+       }
+
+       @Override
+       public Map<String, Object> getAttributes() {
+               return attributes;
+       }
+
+       @Override
+       protected ServletContextHandler getServletContextHandler() {
+               return servletContextHandler;
+       }
+
+}
index 5876d52e8ae72027c5a9deaa68e815affbf89ae9..551e54e05044410f334694cf9c5641b512ee2a48 100644 (file)
@@ -1,23 +1,15 @@
 package org.argeo.cms.jetty;
 
-import java.util.AbstractMap;
 import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 
 import javax.servlet.ServletContext;
 import javax.websocket.DeploymentException;
 import javax.websocket.server.ServerContainer;
 
-import org.argeo.cms.servlet.httpserver.HttpContextServlet;
 import org.argeo.cms.websocket.server.WebsocketEndpoints;
-import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
 import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
 
@@ -27,12 +19,13 @@ import com.sun.net.httpserver.HttpContext;
 import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpServer;
 
-/** Trivial implementation of @{HttpContext}. */
-class JettyHttpContext extends HttpContext {
+/**
+ * An @{HttpContext} implementation based on Jetty. It supports web sockets if
+ * the handler implements {@link WebsocketEndpoints}.
+ */
+abstract class JettyHttpContext extends HttpContext {
        private final JettyHttpServer httpServer;
        private final String path;
-       private final ServletContextHandler contextHandler;
-       private final ContextAttributes attributes;
        private final List<Filter> filters = new ArrayList<>();
 
        private HttpHandler handler;
@@ -40,22 +33,13 @@ class JettyHttpContext extends HttpContext {
 
        public JettyHttpContext(JettyHttpServer httpServer, String path) {
                this.httpServer = httpServer;
+               if (!path.endsWith("/"))
+                       throw new IllegalArgumentException("Path " + path + " should end with a /");
                this.path = path;
-
-               // Jetty context handler
-               ServletContextHandler servletContextHandler = new ServletContextHandler();
-               servletContextHandler.setContextPath(path);
-               HttpContextServlet servlet = new HttpContextServlet(this);
-               servletContextHandler.addServlet(new ServletHolder(servlet), "/*");
-               SessionHandler sessionHandler = new SessionHandler();
-               // FIXME find a better default
-               sessionHandler.setMaxInactiveInterval(-1);
-               servletContextHandler.setSessionHandler(sessionHandler);
-               contextHandler = servletContextHandler;
-
-               attributes = new ContextAttributes();
        }
 
+       protected abstract ServletContextHandler getServletContextHandler();
+
        @Override
        public HttpHandler getHandler() {
                return handler;
@@ -67,32 +51,6 @@ class JettyHttpContext extends HttpContext {
                        throw new IllegalArgumentException("Handler is already set");
                Objects.requireNonNull(handler);
                this.handler = handler;
-
-               // web socket
-               if (handler instanceof WebsocketEndpoints) {
-                       JavaxWebSocketServletContainerInitializer.configure(contextHandler, new Configurator() {
-
-                               @Override
-                               public void accept(ServletContext servletContext, ServerContainer serverContainer)
-                                               throws DeploymentException {
-//                                     CmsWebSocketConfigurator wsEndpointConfigurator = new CmsWebSocketConfigurator();
-
-                                       for (Class<?> clss : ((WebsocketEndpoints) handler).getEndPoints()) {
-//                                             Class<?> clss = websocketEndpoints.get(path);
-//                                             ServerEndpointConfig config = ServerEndpointConfig.Builder.create(clss, path)
-//                                                             .configurator(wsEndpointConfigurator).build();
-                                               serverContainer.addEndpoint(clss);
-                                       }
-                               }
-                       });
-               }
-
-               if (httpServer.isStarted())
-                       try {
-                               contextHandler.start();
-                       } catch (Exception e) {
-                               throw new IllegalStateException("Cannot start context handler", e);
-                       }
        }
 
        @Override
@@ -102,12 +60,11 @@ class JettyHttpContext extends HttpContext {
 
        @Override
        public HttpServer getServer() {
-               return httpServer;
+               return getJettyHttpServer();
        }
 
-       @Override
-       public Map<String, Object> getAttributes() {
-               return attributes;
+       protected JettyHttpServer getJettyHttpServer() {
+               return httpServer;
        }
 
        @Override
@@ -127,51 +84,4 @@ class JettyHttpContext extends HttpContext {
                return authenticator;
        }
 
-       ServletContextHandler getContextHandler() {
-               return contextHandler;
-       }
-
-       private class ContextAttributes extends AbstractMap<String, Object> {
-               @Override
-               public Set<Entry<String, Object>> entrySet() {
-                       Set<Entry<String, Object>> entries = new HashSet<>();
-                       for (Enumeration<String> keys = contextHandler.getAttributeNames(); keys.hasMoreElements();) {
-                               entries.add(new ContextAttributeEntry(keys.nextElement()));
-                       }
-                       return entries;
-               }
-
-               @Override
-               public Object put(String key, Object value) {
-                       Object previousValue = get(key);
-                       contextHandler.setAttribute(key, value);
-                       return previousValue;
-               }
-
-               private class ContextAttributeEntry implements Map.Entry<String, Object> {
-                       private final String key;
-
-                       public ContextAttributeEntry(String key) {
-                               this.key = key;
-                       }
-
-                       @Override
-                       public String getKey() {
-                               return key;
-                       }
-
-                       @Override
-                       public Object getValue() {
-                               return contextHandler.getAttribute(key);
-                       }
-
-                       @Override
-                       public Object setValue(Object value) {
-                               Object previousValue = getValue();
-                               contextHandler.setAttribute(key, value);
-                               return previousValue;
-                       }
-
-               }
-       }
 }
index fd0ef4cac3143046ebea1c852535bbfe464129fe..11cce74cdc2e1b50a0b8294fa0b30a8d61a27c45 100644 (file)
@@ -8,6 +8,7 @@ import java.util.concurrent.Executor;
 import java.util.concurrent.ThreadPoolExecutor;
 
 import javax.servlet.ServletException;
+import javax.websocket.server.ServerContainer;
 
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsState;
@@ -52,6 +53,7 @@ public class JettyHttpServer extends HttpsServer {
 
        private final Map<String, JettyHttpContext> contexts = new TreeMap<>();
 
+       private ServletContextHandler rootContextHandler;
        protected final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection();
 
        private boolean started;
@@ -92,7 +94,7 @@ public class JettyHttpServer extends HttpsServer {
                        // holder
 
                        // context
-                       ServletContextHandler rootContextHandler = createRootContextHandler();
+                       rootContextHandler = createRootContextHandler();
                        // httpContext.addServlet(holder, "/*");
                        if (rootContextHandler != null)
                                configureRootContextHandler(rootContextHandler);
@@ -128,6 +130,78 @@ public class JettyHttpServer extends HttpsServer {
                }
        }
 
+       protected void configureConnectors() {
+               HttpConfiguration httpConfiguration = new HttpConfiguration();
+
+               String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT);
+               String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT);
+               if (httpPortStr != null && httpsPortStr != null)
+                       throw new IllegalArgumentException("Either an HTTP or an HTTPS port should be configured, not both");
+               if (httpPortStr == null && httpsPortStr == null)
+                       throw new IllegalArgumentException("Neither an HTTP or HTTPS port was configured");
+
+               /// TODO make it more generic
+               String httpHost = getDeployProperty(CmsDeployProperty.HOST);
+
+               // try {
+               if (httpPortStr != null || httpsPortStr != null) {
+                       // TODO deal with hostname resolving taking too much time
+//                     String fallBackHostname = InetAddress.getLocalHost().getHostName();
+
+                       boolean httpEnabled = httpPortStr != null;
+                       boolean httpsEnabled = httpsPortStr != null;
+
+                       if (httpsEnabled) {
+                               int httpsPort = Integer.parseInt(httpsPortStr);
+                               httpConfiguration.setSecureScheme("https");
+                               httpConfiguration.setSecurePort(httpsPort);
+                       }
+
+                       if (httpEnabled) {
+                               int httpPort = Integer.parseInt(httpPortStr);
+                               httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
+                               httpConnector.setPort(httpPort);
+                               httpConnector.setHost(httpHost);
+                               httpConnector.setIdleTimeout(DEFAULT_IDLE_TIMEOUT);
+
+                       }
+
+                       if (httpsEnabled) {
+                               SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+                               // sslContextFactory.setKeyStore(KeyS)
+
+                               sslContextFactory.setKeyStoreType(getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE));
+                               sslContextFactory.setKeyStorePath(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE));
+                               sslContextFactory.setKeyStorePassword(getDeployProperty(CmsDeployProperty.SSL_PASSWORD));
+                               // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD));
+                               sslContextFactory.setProtocol("TLS");
+
+                               sslContextFactory.setTrustStoreType(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
+                               sslContextFactory.setTrustStorePath(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE));
+                               sslContextFactory.setTrustStorePassword(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
+
+                               String wantClientAuth = getDeployProperty(CmsDeployProperty.SSL_WANTCLIENTAUTH);
+                               if (wantClientAuth != null && wantClientAuth.equals(Boolean.toString(true)))
+                                       sslContextFactory.setWantClientAuth(true);
+                               String needClientAuth = getDeployProperty(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
+                               if (needClientAuth != null && needClientAuth.equals(Boolean.toString(true)))
+                                       sslContextFactory.setNeedClientAuth(true);
+
+                               // HTTPS Configuration
+                               HttpConfiguration https_config = new HttpConfiguration(httpConfiguration);
+                               https_config.addCustomizer(new SecureRequestCustomizer());
+                               https_config.setUriCompliance(UriCompliance.LEGACY);
+
+                               // HTTPS connector
+                               httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"),
+                                               new HttpConnectionFactory(https_config));
+                               int httpsPort = Integer.parseInt(httpsPortStr);
+                               httpsConnector.setPort(httpsPort);
+                               httpsConnector.setHost(httpHost);
+                       }
+               }
+       }
+
        @Override
        public void stop(int delay) {
                // TODO wait for processing to complete
@@ -169,10 +243,13 @@ public class JettyHttpServer extends HttpsServer {
        public synchronized HttpContext createContext(String path) {
                if (contexts.containsKey(path))
                        throw new IllegalArgumentException("Context " + path + " already exists");
-               JettyHttpContext httpContext = new JettyHttpContext(this, path);
+               if (!path.endsWith("/"))
+                       throw new IllegalArgumentException("Path " + path + " should end with a /");
+
+               JettyHttpContext httpContext = new ServletHttpContext(this, path);
                contexts.put(path, httpContext);
 
-               contextHandlerCollection.addHandler(httpContext.getContextHandler());
+               contextHandlerCollection.addHandler(httpContext.getServletContextHandler());
                return httpContext;
        }
 
@@ -181,8 +258,10 @@ public class JettyHttpServer extends HttpsServer {
                if (!contexts.containsKey(path))
                        throw new IllegalArgumentException("Context " + path + " does not exist");
                JettyHttpContext httpContext = contexts.remove(path);
-               // TODO stop handler first?
-               contextHandlerCollection.removeHandler(httpContext.getContextHandler());
+               if (httpContext instanceof ContextHandlerHttpContext contextHandlerHttpContext) {
+                       // TODO stop handler first?
+                       contextHandlerCollection.removeHandler(contextHandlerHttpContext.getServletContextHandler());
+               }
        }
 
        @Override
@@ -208,81 +287,6 @@ public class JettyHttpServer extends HttpsServer {
                return httpsConfigurator;
        }
 
-       protected void configureConnectors() {
-               HttpConfiguration httpConfiguration = new HttpConfiguration();
-
-               String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT);
-               String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT);
-               if (httpPortStr != null && httpsPortStr != null)
-                       throw new IllegalArgumentException("Either an HTTP or an HTTPS port should be configured, not both");
-               if (httpPortStr == null && httpsPortStr == null)
-                       throw new IllegalArgumentException("Neither an HTTP or HTTPS port was configured");
-
-               /// TODO make it more generic
-               String httpHost = getDeployProperty(CmsDeployProperty.HOST);
-
-               // try {
-               if (httpPortStr != null || httpsPortStr != null) {
-                       // TODO deal with hostname resolving taking too much time
-//                     String fallBackHostname = InetAddress.getLocalHost().getHostName();
-
-                       boolean httpEnabled = httpPortStr != null;
-                       boolean httpsEnabled = httpsPortStr != null;
-
-                       if (httpsEnabled) {
-                               int httpsPort = Integer.parseInt(httpsPortStr);
-                               httpConfiguration.setSecureScheme("https");
-                               httpConfiguration.setSecurePort(httpsPort);
-                       }
-
-                       if (httpEnabled) {
-                               int httpPort = Integer.parseInt(httpPortStr);
-                               httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
-                               httpConnector.setPort(httpPort);
-                               httpConnector.setHost(httpHost);
-                               httpConnector.setIdleTimeout(DEFAULT_IDLE_TIMEOUT);
-
-                       }
-
-                       if (httpsEnabled) {
-
-                               SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
-                               // sslContextFactory.setKeyStore(KeyS)
-
-                               sslContextFactory.setKeyStoreType(getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE));
-                               sslContextFactory.setKeyStorePath(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE));
-                               sslContextFactory.setKeyStorePassword(getDeployProperty(CmsDeployProperty.SSL_PASSWORD));
-                               // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD));
-                               sslContextFactory.setProtocol("TLS");
-
-                               sslContextFactory.setTrustStoreType(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
-                               sslContextFactory.setTrustStorePath(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE));
-                               sslContextFactory.setTrustStorePassword(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
-
-                               String wantClientAuth = getDeployProperty(CmsDeployProperty.SSL_WANTCLIENTAUTH);
-                               if (wantClientAuth != null && wantClientAuth.equals(Boolean.toString(true)))
-                                       sslContextFactory.setWantClientAuth(true);
-                               String needClientAuth = getDeployProperty(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
-                               if (needClientAuth != null && needClientAuth.equals(Boolean.toString(true)))
-                                       sslContextFactory.setNeedClientAuth(true);
-
-                               // HTTPS Configuration
-                               HttpConfiguration https_config = new HttpConfiguration(httpConfiguration);
-                               https_config.addCustomizer(new SecureRequestCustomizer());
-                               https_config.setUriCompliance(UriCompliance.LEGACY);
-
-                               // HTTPS connector
-                               httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"),
-                                               new HttpConnectionFactory(https_config));
-                               int httpsPort = Integer.parseInt(httpsPortStr);
-                               httpsConnector.setPort(httpsPort);
-                               httpsConnector.setHost(httpHost);
-                       }
-
-               }
-
-       }
-
        protected String getDeployProperty(CmsDeployProperty deployProperty) {
                return cmsState != null ? cmsState.getDeployProperty(deployProperty.getProperty())
                                : System.getProperty(deployProperty.getProperty());
@@ -290,7 +294,7 @@ public class JettyHttpServer extends HttpsServer {
 
        private String httpPortsMsg() {
 
-               return (httpConnector != null ? "HTTP " + getHttpPort() + " " : " ")
+               return (httpConnector != null ? "HTTP " + getHttpPort() + " " : "")
                                + (httpsConnector != null ? "HTTPS " + getHttpsPort() : "");
        }
 
@@ -318,10 +322,18 @@ public class JettyHttpServer extends HttpsServer {
                this.cmsState = cmsState;
        }
 
-       public boolean isStarted() {
+       boolean isStarted() {
                return started;
        }
 
+       ServletContextHandler getRootContextHandler() {
+               return rootContextHandler;
+       }
+
+       ServerContainer getRootServerContainer() {
+               throw new UnsupportedOperationException();
+       }
+
        public static void main(String... args) {
                JettyHttpServer httpServer = new JettyHttpServer();
                System.setProperty("argeo.http.port", "8080");
diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java
new file mode 100644 (file)
index 0000000..3361194
--- /dev/null
@@ -0,0 +1,64 @@
+package org.argeo.cms.jetty;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.servlet.httpserver.HttpContextServlet;
+import org.argeo.cms.websocket.server.WebsocketEndpoints;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * A {@link JettyHttpContext} based on registering a servlet to the root handler
+ * of the {@link JettyHttpServer}, in order to integrate the sessions.
+ */
+public class ServletHttpContext extends JettyHttpContext {
+       private final static CmsLog log = CmsLog.getLog(ServletHttpContext.class);
+
+       private Map<String, Object> attributes = Collections.synchronizedMap(new HashMap<>());
+
+       public ServletHttpContext(JettyHttpServer httpServer, String path) {
+               super(httpServer, path);
+
+               ServletContextHandler rootContextHandler = httpServer.getRootContextHandler();
+               HttpContextServlet servlet = new HttpContextServlet(this);
+               rootContextHandler.addServlet(new ServletHolder(servlet), path + "*");
+       }
+
+       @Override
+       public void setHandler(HttpHandler handler) {
+               super.setHandler(handler);
+
+               // web socket
+               if (handler instanceof WebsocketEndpoints) {
+                       ServerContainer serverContainer = getJettyHttpServer().getRootServerContainer();
+                       for (Class<?> clss : ((WebsocketEndpoints) handler).getEndPoints()) {
+                               try {
+                                       serverContainer.addEndpoint(clss);
+                                       log.debug(() -> "Added web socket " + clss + " to " + getPath());
+                               } catch (DeploymentException e) {
+                                       log.error("Cannot deploy Web Socket " + clss, e);
+                               }
+                       }
+               }
+       }
+
+       @Override
+       public Map<String, Object> getAttributes() {
+               return attributes;
+       }
+
+       @Override
+       protected ServletContextHandler getServletContextHandler() {
+               return getJettyHttpServer().getRootContextHandler();
+       }
+
+}
index 6b05a900445c9fe94c0372688b87444a5ccc892b..afd4e0aaf06f6f559301c717226df04ca78cf6e6 100644 (file)
@@ -4,6 +4,6 @@
    <service>
       <provide interface="com.sun.net.httpserver.HttpHandler"/>
    </service>
-   <property name="context.path" type="String" value="/api/acr" />
+   <property name="context.path" type="String" value="/api/acr/" />
    <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ProvidedRepository" policy="static"/>
 </scr:component>
index d2ceb27a509860ece72d1c36136c70fb053fcb38..772531ede5f53f213a10c8cbc510d275542809f4 100644 (file)
@@ -22,7 +22,7 @@ import org.argeo.cms.internal.runtime.CmsStateImpl;
 import org.argeo.util.http.HttpHeader;
 import org.osgi.service.useradmin.Authorization;
 
-/** Use the HTTP session as the basis for authentication. */
+/** Use a remote session as the basis for authentication. */
 public class RemoteSessionLoginModule implements LoginModule {
        private final static CmsLog log = CmsLog.getLog(RemoteSessionLoginModule.class);
 
@@ -64,8 +64,6 @@ public class RemoteSessionLoginModule implements LoginModule {
                                return false;
                        // TODO factorize with below
                        String httpSessionId = httpSession.getId();
-//                     if (log.isTraceEnabled())
-//                             log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
                        CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId);
                        if (cmsSession != null && !cmsSession.isAnonymous()) {
                                authorization = cmsSession.getAuthorization();
@@ -77,16 +75,8 @@ public class RemoteSessionLoginModule implements LoginModule {
                        authorization = (Authorization) request.getAttribute(RemoteAuthRequest.AUTHORIZATION);
                        if (authorization == null) {// search by session ID
                                RemoteAuthSession httpSession = request.getSession();
-//                             if (httpSession == null) {
-//                                     // TODO make sure this is always safe
-//                                     if (log.isTraceEnabled())
-//                                             log.trace("Create http session");
-//                                     httpSession = request.createSession();
-//                             }
                                if (httpSession != null) {
                                        String httpSessionId = httpSession.getId();
-//                             if (log.isTraceEnabled())
-//                                     log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
                                        CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId);
                                        if (cmsSession != null && !cmsSession.isAnonymous()) {
                                                authorization = cmsSession.getAuthorization();
@@ -191,15 +181,6 @@ public class RemoteSessionLoginModule implements LoginModule {
                                }
                        }
                }
-
-               // auth token
-               // String mail = request.getParameter(LdapAttrs.mail.name());
-               // String authPassword = request.getParameter(LdapAttrs.authPassword.name());
-               // if (authPassword != null) {
-               // sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, authPassword);
-               // if (mail != null)
-               // sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, mail);
-               // }
        }
 
        private void extractClientCertificate(RemoteAuthRequest req) {
index 294b485fb450668352baac32a63e457ebd30233d..ed20e95b271a7b7168ad43821fcf0fbf96b9896c 100644 (file)
@@ -7,59 +7,80 @@ import java.net.http.WebSocket;
 import java.nio.ByteBuffer;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
 
 import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
 
 import org.argeo.cms.auth.RemoteAuthUtils;
 import org.argeo.util.http.HttpHeader;
 
 /** Tests connectivity to the web socket server. */
-public class WebSocketEventClient {
+public class WebSocketEventClient implements Runnable {
 
-       public static void main(String[] args) throws Exception {
-               if (args.length == 0) {
-                       System.err.println("usage: java " + WebSocketEventClient.class.getName() + " <url>");
-                       System.exit(1);
-                       return;
-               }
-               URI uri = URI.create(args[0]);
-               WebSocket.Listener listener = new WebSocket.Listener() {
+       private final URI uri;
 
-                       public CompletionStage<?> onText(WebSocket webSocket, CharSequence message, boolean last) {
-                               System.out.println(message);
-                               CompletionStage<String> res = CompletableFuture.completedStage(message.toString());
-                               return res;
-                       }
+       private WebSocket webSocket;
+       
+       public WebSocketEventClient(URI uri) {
+               this.uri = uri;
+       }
 
-                       @Override
-                       public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
-                               // System.out.println("Pong received.");
-                               return null;
-                       }
+       @Override
+       public void run() {
+               try {
+                       WebSocket.Listener listener = new WebSocket.Listener() {
 
-               };
+                               public CompletionStage<?> onText(WebSocket webSocket, CharSequence message, boolean last) {
+                                       System.out.println(message);
+                                       CompletionStage<String> res = CompletableFuture.completedStage(message.toString());
+                                       return res;
+                               }
 
-               // SPNEGO
-               URL jaasUrl = SpnegoHttpClient.class.getResource("jaas-client-ipa.cfg");
-               System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
-               LoginContext lc = new LoginContext(SpnegoHttpClient.CLIENT_LOGIN_CONTEXT);
-               lc.login();
-               String token = RemoteAuthUtils.createGssToken(lc.getSubject(), "HTTP", uri.getHost());
+                               @Override
+                               public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
+                                       // System.out.println("Pong received.");
+                                       return null;
+                               }
 
-               HttpClient client = SpnegoHttpClient.openHttpClient(lc.getSubject());
-               CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
-                               .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + token)
-                               .buildAsync(uri, listener);
+                       };
 
-               WebSocket webSocket = ws.get();
-               webSocket.request(Long.MAX_VALUE);
+                       // SPNEGO
+                       URL jaasUrl = SpnegoHttpClient.class.getResource("jaas-client-ipa.cfg");
+                       System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
+                       LoginContext lc = new LoginContext(SpnegoHttpClient.CLIENT_LOGIN_CONTEXT);
+                       lc.login();
+                       String token = RemoteAuthUtils.createGssToken(lc.getSubject(), "HTTP", uri.getHost());
 
-               Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
+                       HttpClient client = SpnegoHttpClient.openHttpClient(lc.getSubject());
+                       CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
+                                       .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + token)
+                                       .buildAsync(uri, listener);
 
-               while (!webSocket.isInputClosed()) {
-                       webSocket.sendPing(ByteBuffer.allocate(0));
-                       Thread.sleep(10000);
+                       WebSocket webSocket = ws.get();
+                       webSocket.request(Long.MAX_VALUE);
+
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
+
+                       while (!webSocket.isInputClosed()) {
+                               webSocket.sendPing(ByteBuffer.allocate(0));
+                               Thread.sleep(10000);
+                       }
+               }catch (InterruptedException e) {
+                       if (webSocket != null)
+                               webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
+               } catch (ExecutionException | LoginException e) {
+                       throw new RuntimeException("Cannot listent to " + uri, e.getCause());
                }
        }
 
+//     public static void main(String[] args) throws Exception {
+//             if (args.length == 0) {
+//                     System.err.println("usage: java " + WebSocketEventClient.class.getName() + " <url>");
+//                     System.exit(1);
+//                     return;
+//             }
+//             URI uri = URI.create(args[0]);
+//     }
+
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/client/WebSocketPing.java b/org.argeo.cms/src/org/argeo/cms/client/WebSocketPing.java
new file mode 100644 (file)
index 0000000..808c8de
--- /dev/null
@@ -0,0 +1,90 @@
+package org.argeo.cms.client;
+
+import java.math.RoundingMode;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+
+/** Tests connectivity to the web socket server. */
+public class WebSocketPing implements Runnable {
+       private final static int PING_FRAME_SIZE = 125;
+       private final static DecimalFormat decimalFormat = new DecimalFormat("0.0");
+       static {
+               decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
+       }
+
+       private final URI uri;
+       private final UUID uuid;
+
+       private WebSocket webSocket;
+
+       public WebSocketPing(URI uri) {
+               this.uri = uri;
+               this.uuid = UUID.randomUUID();
+       }
+
+       @Override
+       public void run() {
+               try {
+                       WebSocket.Listener listener = new WebSocket.Listener() {
+
+                               @Override
+                               public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
+                                       long msb = message.getLong();
+                                       long lsb = message.getLong();
+                                       long end = System.nanoTime();
+                                       if (msb != uuid.getMostSignificantBits() || lsb != uuid.getLeastSignificantBits())
+                                               return null; // ignore
+                                       long begin = message.getLong();
+                                       double durationNs = end - begin;
+                                       double durationMs = durationNs / 1000000;
+                                       int size = message.remaining() + (3 * Long.BYTES);
+                                       System.out.println(
+                                                       size + " bytes from " + uri + ": time=" + decimalFormat.format(durationMs) + " ms");
+                                       return null;
+                               }
+
+                       };
+
+                       HttpClient client = HttpClient.newHttpClient();
+                       CompletableFuture<WebSocket> ws = client.newWebSocketBuilder().buildAsync(uri, listener);
+                       webSocket = ws.get();
+                       webSocket.request(Long.MAX_VALUE);
+
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
+
+                       while (!webSocket.isInputClosed()) {
+                               long begin = System.nanoTime();
+                               ByteBuffer buffer = ByteBuffer.allocate(PING_FRAME_SIZE);
+                               buffer.putLong(uuid.getMostSignificantBits());
+                               buffer.putLong(uuid.getLeastSignificantBits());
+                               buffer.putLong(begin);
+                               buffer.flip();
+                               webSocket.sendPing(buffer);
+                               Thread.sleep(1000);
+                       }
+               } catch (InterruptedException e) {
+                       if (webSocket != null)
+                               webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
+               } catch (ExecutionException e) {
+                       throw new RuntimeException("Cannot ping " + uri, e.getCause());
+               }
+       }
+
+//     public static void main(String[] args) throws Exception {
+//             if (args.length == 0) {
+//                     System.err.println("usage: java " + WsPing.class.getName() + " <url>");
+//                     System.exit(1);
+//                     return;
+//             }
+//             URI uri = URI.create(args[0]);
+//             new WsPing(uri).run();
+//     }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/client/WsPing.java b/org.argeo.cms/src/org/argeo/cms/client/WsPing.java
deleted file mode 100644 (file)
index caf6796..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.argeo.cms.client;
-
-import java.math.RoundingMode;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.WebSocket;
-import java.nio.ByteBuffer;
-import java.text.DecimalFormat;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ExecutionException;
-
-/** Tests connectivity to the web socket server. */
-public class WsPing implements Runnable {
-       private final static int PING_FRAME_SIZE = 125;
-       private final static DecimalFormat decimalFormat = new DecimalFormat("0.0");
-       static {
-               decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
-       }
-
-       private final URI uri;
-       private final UUID uuid;
-
-       private WebSocket webSocket;
-
-       public WsPing(URI uri) {
-               this.uri = uri;
-               this.uuid = UUID.randomUUID();
-       }
-
-       @Override
-       public void run() {
-               try {
-                       WebSocket.Listener listener = new WebSocket.Listener() {
-
-                               @Override
-                               public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
-                                       long msb = message.getLong();
-                                       long lsb = message.getLong();
-                                       long end = System.nanoTime();
-                                       if (msb != uuid.getMostSignificantBits() || lsb != uuid.getLeastSignificantBits())
-                                               return null; // ignore
-                                       long begin = message.getLong();
-                                       double durationNs = end - begin;
-                                       double durationMs = durationNs / 1000000;
-                                       int size = message.remaining() + (3 * Long.BYTES);
-                                       System.out.println(
-                                                       size + " bytes from " + uri + ": time=" + decimalFormat.format(durationMs) + " ms");
-                                       return null;
-                               }
-
-                       };
-
-                       HttpClient client = HttpClient.newHttpClient();
-                       CompletableFuture<WebSocket> ws = client.newWebSocketBuilder().buildAsync(uri, listener);
-                       webSocket = ws.get();
-                       webSocket.request(Long.MAX_VALUE);
-
-                       Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
-
-                       while (!webSocket.isInputClosed()) {
-                               long begin = System.nanoTime();
-                               ByteBuffer buffer = ByteBuffer.allocate(PING_FRAME_SIZE);
-                               buffer.putLong(uuid.getMostSignificantBits());
-                               buffer.putLong(uuid.getLeastSignificantBits());
-                               buffer.putLong(begin);
-                               buffer.flip();
-                               webSocket.sendPing(buffer);
-                               Thread.sleep(1000);
-                       }
-               } catch (InterruptedException e) {
-                       if (webSocket != null)
-                               webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
-               } catch (ExecutionException e) {
-                       throw new RuntimeException("Cannot ping " + uri, e.getCause());
-               }
-       }
-
-//     public static void main(String[] args) throws Exception {
-//             if (args.length == 0) {
-//                     System.err.println("usage: java " + WsPing.class.getName() + " <url>");
-//                     System.exit(1);
-//                     return;
-//             }
-//             URI uri = URI.create(args[0]);
-//             new WsPing(uri).run();
-//     }
-
-}
index 164e9b9b2b24245f913773796d661fc362409a63..5d96244d8ed4cdb1cbb9e77e8346f2168e430af3 100644 (file)
@@ -15,12 +15,8 @@ import com.sun.net.httpserver.Authenticator;
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpPrincipal;
 
+/** An {@link Authenticator} implementation based on CMS authentication. */
 public class CmsAuthenticator extends Authenticator {
-//     final static String HEADER_AUTHORIZATION = "Authorization";
-//     final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-
-//     private final static CmsLog log = CmsLog.getLog(CmsAuthenticator.class);
-
        // TODO make it configurable
        private final String httpAuthRealm = "Argeo";
        private final boolean forceBasic = false;
index c2d2ccf42d4cdd2814e6c4806d366bf9385d935d..bd54b20594b5e7200d0a3e04f975e8cfc13ab354 100644 (file)
@@ -25,19 +25,18 @@ import org.argeo.cms.internal.auth.CmsSessionImpl;
 import org.ietf.jgss.GSSCredential;
 import org.osgi.service.useradmin.UserAdmin;
 
+/** Reference implementation of {@link CmsContext}. */
 public class CmsContextImpl implements CmsContext {
 
        private final CmsLog log = CmsLog.getLog(getClass());
 
        private static CompletableFuture<CmsContextImpl> instance = new CompletableFuture<CmsContextImpl>();
-//     private static CmsContextImpl instance = null;
 
        private CmsState cmsState;
        private CmsDeployment cmsDeployment;
        private UserAdmin userAdmin;
        private UuidFactory uuidFactory;
        private CmsEventBus cmsEventBus;
-//     private ProvidedRepository contentRepository;
 
        // i18n
        private Locale defaultLocale;
@@ -64,9 +63,6 @@ public class CmsContextImpl implements CmsContext {
                                }
                        }
                }, "Check readiness").start();
-
-               // checkReadiness();
-
                setInstance(this);
        }
 
@@ -178,14 +174,6 @@ public class CmsContextImpl implements CmsContext {
                this.uuidFactory = uuidFactory;
        }
 
-//     public ProvidedRepository getContentRepository() {
-//             return contentRepository;
-//     }
-//
-//     public void setContentRepository(ProvidedRepository contentRepository) {
-//             this.contentRepository = contentRepository;
-//     }
-
        @Override
        public Locale getDefaultLocale() {
                return defaultLocale;
@@ -238,15 +226,6 @@ public class CmsContextImpl implements CmsContext {
        }
 
        private static void setInstance(CmsContextImpl cmsContextImpl) {
-//             if (cmsContextImpl != null) {
-//                     if (instance != null)
-//                             throw new IllegalStateException("CMS Context is already set");
-//                     instance = cmsContextImpl;
-//             } else {
-//                     instance = null;
-//             }
-//             CmsContextImpl.class.notifyAll();
-
                if (cmsContextImpl != null) {
                        if (instance.isDone())
                                throw new IllegalStateException("CMS Context is already set");
@@ -259,15 +238,6 @@ public class CmsContextImpl implements CmsContext {
        }
 
        private static CmsContextImpl getInstance() {
-//             while (instance == null) {
-//                     try {
-//                             CmsContextImpl.class.wait();
-//                     } catch (InterruptedException e) {
-//                             throw new IllegalStateException("Cannot wait for CMS context instance", e);
-//                     }
-//             }
-//             return instance;
-
                try {
                        return instance.get();
                } catch (InterruptedException | ExecutionException e) {
index 421ff661aeb2decf812a3f9a2baec23a4ebbd31b..e2d1fb97a592d38e6a64e85918353dbb5b49ce6a 100644 (file)
@@ -18,7 +18,7 @@ import com.sun.net.httpserver.HttpContext;
 import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpServer;
 
-/** Implementation of a CMS deployment. */
+/** Reference implementation of {@link CmsDeployment}. */
 public class CmsDeploymentImpl implements CmsDeployment {
        private final CmsLog log = CmsLog.getLog(getClass());
 
index eaa63756dc05e61800ffdb8668bf4ba200907ff3..99f6c1d8d18a548d6ecb6adfff5b6fd91abf5325 100644 (file)
@@ -10,16 +10,12 @@ import org.argeo.api.cms.CmsEventBus;
 import org.argeo.api.cms.CmsEventSubscriber;
 import org.argeo.api.cms.CmsLog;
 
+/** {@link CmsEventBus} implementation based on {@link Flow}. */
 public class CmsEventBusImpl implements CmsEventBus {
        private final CmsLog log = CmsLog.getLog(CmsEventBus.class);
 
-       // CMS events
        private Map<String, SubmissionPublisher<Map<String, Object>>> topics = new TreeMap<>();
-//     private IdentityHashMap<CmsEventSubscriber, List<CmsEventFlowSubscriber>> subscriptions = new IdentityHashMap<>();
 
-       /*
-        * CMS Events
-        */
        @Override
        public void sendEvent(String topic, Map<String, Object> event) {
                SubmissionPublisher<Map<String, Object>> publisher = topics.get(topic);
@@ -58,6 +54,7 @@ public class CmsEventBusImpl implements CmsEventBus {
                }
        }
 
+       /** A subscriber to a topic. */
        static class CmsEventFlowSubscriber implements Flow.Subscriber<Map<String, Object>> {
                private String topic;
                private CmsEventSubscriber eventSubscriber;