Improve initialisation
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / runtime / CmsDeploymentImpl.java
index 4ffa03a63fffe2ff37d4f18db8494b849275c97a..e1c420b8287469bb698af791d04b9d87638563d9 100644 (file)
 package org.argeo.cms.internal.runtime;
 
-import java.io.IOException;
-import java.net.URL;
-import java.util.Dictionary;
+import static org.argeo.api.cms.CmsConstants.CONTEXT_PATH;
 
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsDeployment;
 import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSshd;
 import org.argeo.api.cms.CmsState;
-import org.argeo.cms.internal.osgi.DeployConfig;
-import org.osgi.service.http.HttpService;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.internal.http.CmsAuthenticator;
+import org.argeo.cms.internal.http.PublicCmsAuthenticator;
+
+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());
-//     private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
 
-//     private Long availableSince;
+       private CmsState cmsState;
 
-       // Readiness
-//     private boolean nodeAvailable = false;
-//     private boolean userAdminAvailable = false;
+       // Expectations
        private boolean httpExpected = false;
-//     private boolean httpAvailable = false;
-       private HttpService httpService;
-
-       private CmsState cmsState;
-       private DeployConfig deployConfig;
+       private boolean sshdExpected = false;
 
-       public CmsDeploymentImpl() {
-//             ServiceReference<NodeState> nodeStateSr = bc.getServiceReference(NodeState.class);
-//             if (nodeStateSr == null)
-//                     throw new CmsException("No node state available");
+       // HTTP
+       private CompletableFuture<HttpServer> httpServer = new CompletableFuture<>();
+       private Map<String, HttpHandler> httpHandlers = new TreeMap<>();
+       private Map<String, CmsAuthenticator> httpAuthenticators = new TreeMap<>();
 
-//             NodeState nodeState = bc.getService(nodeStateSr);
-//             cleanState = nodeState.isClean();
+       // SSHD
+       private CompletableFuture<CmsSshd> cmsSshd = new CompletableFuture<>();
 
-//             nodeHttp = new NodeHttp();
-               initTrackers();
+       public void start() {
+               log.debug(() -> "CMS deployment available");
        }
 
-       private void initTrackers() {
-//             ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
-//
-//                     @Override
-//                     public HttpService addingService(ServiceReference<HttpService> sr) {
-//                             httpAvailable = true;
-//                             Object httpPort = sr.getProperty("http.port");
-//                             Object httpsPort = sr.getProperty("https.port");
-//                             log.info(httpPortsMsg(httpPort, httpsPort));
-//                             checkReadiness();
-//                             return super.addingService(sr);
-//                     }
-//             };
-//             // httpSt.open();
-//             KernelUtils.asyncOpen(httpSt);
-
-//             ServiceTracker<?, ?> userAdminSt = new ServiceTracker<UserAdmin, UserAdmin>(bc, UserAdmin.class, null) {
-//                     @Override
-//                     public UserAdmin addingService(ServiceReference<UserAdmin> reference) {
-//                             UserAdmin userAdmin = super.addingService(reference);
-//                             addStandardSystemRoles(userAdmin);
-//                             userAdminAvailable = true;
-//                             checkReadiness();
-//                             return userAdmin;
-//                     }
-//             };
-//             // userAdminSt.open();
-//             KernelUtils.asyncOpen(userAdminSt);
-
-//             ServiceTracker<?, ?> confAdminSt = new ServiceTracker<ConfigurationAdmin, ConfigurationAdmin>(bc,
-//                             ConfigurationAdmin.class, null) {
-//                     @Override
-//                     public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> reference) {
-//                             ConfigurationAdmin configurationAdmin = bc.getService(reference);
-////                           boolean isClean;
-////                           try {
-////                                   Configuration[] confs = configurationAdmin
-////                                                   .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
-////                                   isClean = confs == null || confs.length == 0;
-////                           } catch (Exception e) {
-////                                   throw new IllegalStateException("Cannot analyse clean state", e);
-////                           }
-//                             deployConfig = new DeployConfig(configurationAdmin, isClean);
-//                             Activator.registerService(CmsDeployment.class, CmsDeploymentImpl.this, null);
-////                           JcrInitUtils.addToDeployment(CmsDeployment.this);
-//                             httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
-//                             try {
-//                                     Configuration[] configs = configurationAdmin
-//                                                     .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
-//
-//                                     boolean hasDomain = false;
-//                                     for (Configuration config : configs) {
-//                                             Object realm = config.getProperties().get(UserAdminConf.realm.name());
-//                                             if (realm != null) {
-//                                                     log.debug("Found realm: " + realm);
-//                                                     hasDomain = true;
-//                                             }
-//                                     }
-//                                     if (hasDomain) {
-//                                             loadIpaJaasConfiguration();
-//                                     }
-//                             } catch (Exception e) {
-//                                     throw new IllegalStateException("Cannot initialize config", e);
-//                             }
-//                             return super.addingService(reference);
-//                     }
-//             };
-//             // confAdminSt.open();
-//             KernelUtils.asyncOpen(confAdminSt);
+       public void stop() {
        }
 
-       public void start() {
-               httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
-               if (deployConfig.hasDomain()) {
-                       loadIpaJaasConfiguration();
-               }
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+
+               String httpPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTP_PORT.getProperty());
+               String httpsPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTPS_PORT.getProperty());
+               httpExpected = httpPort != null || httpsPort != null;
+               if (!httpExpected)
+                       httpServer.complete(null);
 
-//             while (!isHttpAvailableOrNotExpected()) {
-//                     try {
-//                             Thread.sleep(100);
-//                     } catch (InterruptedException e) {
-//                             log.error("Interrupted while waiting for http");
-//                     }
-//             }
+               String sshdPort = this.cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty());
+               sshdExpected = sshdPort != null;
+               if (!sshdExpected)
+                       cmsSshd.complete(null);
        }
 
-       public void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
-               deployConfig.putFactoryDeployConfig(factoryPid, props);
-               deployConfig.save();
-               try {
-                       deployConfig.loadConfigs();
-               } catch (IOException e) {
-                       throw new IllegalStateException(e);
+       public void setHttpServer(HttpServer httpServer) {
+               Objects.requireNonNull(httpServer);
+               this.httpServer.complete(httpServer);
+               // create contexts whose handles had already been published
+               for (String contextPath : httpHandlers.keySet()) {
+                       HttpHandler httpHandler = httpHandlers.get(contextPath);
+                       CmsAuthenticator authenticator = httpAuthenticators.get(contextPath);
+                       createHttpContext(contextPath, httpHandler, authenticator);
                }
        }
 
-       public Dictionary<String, Object> getProps(String factoryPid, String cn) {
-               return deployConfig.getProps(factoryPid, cn);
+       public void addHttpHandler(HttpHandler httpHandler, Map<String, String> properties) {
+               final String contextPath = properties.get(CONTEXT_PATH);
+               if (contextPath == null) {
+                       log.warn("Property " + CONTEXT_PATH + " not set on HTTP handler " + properties + ". Ignoring it.");
+                       return;
+               }
+               boolean isPublic = Boolean.parseBoolean(properties.get(CmsConstants.CONTEXT_PUBLIC));
+               CmsAuthenticator authenticator = isPublic ? new PublicCmsAuthenticator() : new CmsAuthenticator();
+               httpHandlers.put(contextPath, httpHandler);
+               httpAuthenticators.put(contextPath, authenticator);
+               if (httpServer.join() == null) {
+                       return;
+               } else {
+                       createHttpContext(contextPath, httpHandler, authenticator);
+               }
        }
 
-//     private void addStandardSystemRoles(UserAdmin userAdmin) {
-//             // we assume UserTransaction is already available (TODO make it more robust)
-//             WorkTransaction userTransaction = bc.getService(bc.getServiceReference(WorkTransaction.class));
-//             try {
-//                     userTransaction.begin();
-//                     Role adminRole = userAdmin.getRole(CmsConstants.ROLE_ADMIN);
-//                     if (adminRole == null) {
-//                             adminRole = userAdmin.createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
-//                     }
-//                     if (userAdmin.getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
-//                             Group userAdminRole = (Group) userAdmin.createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
-//                             userAdminRole.addMember(adminRole);
-//                     }
-//                     userTransaction.commit();
-//             } catch (Exception e) {
-//                     try {
-//                             userTransaction.rollback();
-//                     } catch (Exception e1) {
-//                             // silent
-//                     }
-//                     throw new IllegalStateException("Cannot add standard system roles", e);
-//             }
-//     }
-
-       public boolean isHttpAvailableOrNotExpected() {
-               return (httpExpected ? httpService != null : true);
+       public void createHttpContext(String contextPath, HttpHandler httpHandler, CmsAuthenticator authenticator) {
+               if (!httpExpected) {
+                       if (log.isTraceEnabled())
+                               log.warn("Ignore HTTP context " + contextPath + " as we don't provide an HTTP server");
+                       return;
+               }
+               HttpContext httpContext = httpServer.join().createContext(contextPath);
+               // we want to set the authenticator BEFORE the handler actually becomes active
+               httpContext.setAuthenticator(authenticator);
+               httpContext.setHandler(httpHandler);
+               log.debug(() -> "Added handler " + contextPath + " : " + httpHandler.getClass().getName());
        }
 
-       private void loadIpaJaasConfiguration() {
-               if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
-                       String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
-                       URL url = getClass().getClassLoader().getResource(jaasConfig);
-                       KernelUtils.setJaasConfiguration(url);
-                       log.debug("Set IPA JAAS configuration.");
-               }
+       public void removeHttpHandler(HttpHandler httpHandler, Map<String, String> properties) {
+               final String contextPath = properties.get(CmsConstants.CONTEXT_PATH);
+               if (contextPath == null)
+                       return; // ignore silently
+               httpHandlers.remove(contextPath);
+               if (httpServer.join() == null)
+                       return;
+               httpServer.join().removeContext(contextPath);
+               log.debug(() -> "Removed handler " + contextPath + " : " + httpHandler.getClass().getName());
        }
 
-       public void stop() {
-//             if (nodeHttp != null)
-//                     nodeHttp.destroy();
-
-//             try {
-//                     JettyConfigurator.stopServer(KernelConstants.DEFAULT_JETTY_SERVER);
-//             } catch (Exception e) {
-//                     log.error("Cannot stop default Jetty server.", e);
-//             }
-
-               if (deployConfig != null) {
-                       deployConfig.save();
-                       // new Thread(() -> deployConfig.save(), "Save Argeo Deploy Config").start();
-               }
+       public boolean allExpectedServicesAvailable() {
+               if (httpExpected && !httpServer.isDone())
+                       return false;
+               if (sshdExpected && !cmsSshd.isDone())
+                       return false;
+               return true;
        }
 
-       public void setDeployConfig(DeployConfig deployConfig) {
-               this.deployConfig = deployConfig;
+       public void setCmsSshd(CmsSshd cmsSshd) {
+               Objects.requireNonNull(cmsSshd);
+               this.cmsSshd.complete(cmsSshd);
        }
 
-       public void setCmsState(CmsState cmsState) {
-               this.cmsState = cmsState;
+       @Override
+       public CompletionStage<HttpServer> getHttpServer() {
+               return httpServer.minimalCompletionStage();
        }
 
-       public void setHttpService(HttpService httpService) {
-               this.httpService = httpService;
+       @Override
+       public CompletionStage<CmsSshd> getCmsSshd() {
+               return cmsSshd.minimalCompletionStage();
        }
 
 }