Refactor http
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / CmsDeployment.java
index 6fbe7a95c50964d5a4489b978456359fb879303d..6faa4c9e2ee2e36ed69150949ab5e528548d3ecf 100644 (file)
@@ -4,8 +4,8 @@ import static org.argeo.node.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE;
 
 import java.io.InputStreamReader;
 import java.io.Reader;
+import java.lang.management.ManagementFactory;
 import java.net.URL;
-import java.util.Dictionary;
 import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.List;
@@ -14,43 +14,176 @@ import java.util.Set;
 
 import javax.jcr.Repository;
 import javax.jcr.Session;
+import javax.security.auth.callback.CallbackHandler;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.jackrabbit.commons.cnd.CndImporter;
+import org.apache.jackrabbit.core.RepositoryContext;
 import org.argeo.cms.CmsException;
-import org.argeo.jcr.ArgeoJcrConstants;
+import org.argeo.cms.internal.http.NodeHttp;
 import org.argeo.jcr.JcrUtils;
 import org.argeo.node.DataModelNamespace;
+import org.argeo.node.NodeConstants;
 import org.argeo.node.NodeDeployment;
+import org.argeo.node.NodeState;
+import org.argeo.node.security.CryptoKeyring;
+import org.argeo.util.LangUtils;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
 import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.wiring.BundleCapability;
 import org.osgi.framework.wiring.BundleWire;
 import org.osgi.framework.wiring.BundleWiring;
-import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
 import org.osgi.service.cm.ManagedService;
+import org.osgi.service.useradmin.UserAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class CmsDeployment implements NodeDeployment {
+       private final static String LEGACY_JCR_REPOSITORY_ALIAS = "argeo.jcr.repository.alias";
 
-public class CmsDeployment implements NodeDeployment, ManagedService {
        private final Log log = LogFactory.getLog(getClass());
        private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
 
-       private Repository deployedNodeRepository;
+       private DeployConfig deployConfig;
        private HomeRepository homeRepository;
-       
-       @Override
-       public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
-               if (properties == null)
-                       return;
 
+       private Long availableSince;
+
+       private final boolean cleanState;
+
+       private NodeHttp nodeHttp;
+
+       // Readiness
+       private boolean nodeAvailable = false;
+       private boolean userAdminAvailable = false;
+       private boolean httpExpected = false;
+       private boolean httpAvailable = false;
+
+       public CmsDeployment() {
+               ServiceReference<NodeState> nodeStateSr = bc.getServiceReference(NodeState.class);
+               if (nodeStateSr == null)
+                       throw new CmsException("No node state available");
+
+               NodeState nodeState = bc.getService(nodeStateSr);
+               cleanState = nodeState.isClean();
+
+               nodeHttp = new NodeHttp();
+               initTrackers();
+       }
+
+       private void initTrackers() {
+               new ServiceTracker<NodeHttp, NodeHttp>(bc, NodeHttp.class, null) {
+
+                       @Override
+                       public NodeHttp addingService(ServiceReference<NodeHttp> reference) {
+                               httpAvailable = true;
+                               checkReadiness();
+                               return super.addingService(reference);
+                       }
+               }.open();
+               new RepositoryContextStc().open();
+               new ServiceTracker<UserAdmin, UserAdmin>(bc, UserAdmin.class, null) {
+                       @Override
+                       public UserAdmin addingService(ServiceReference<UserAdmin> reference) {
+                               userAdminAvailable = true;
+                               checkReadiness();
+                               return super.addingService(reference);
+                       }
+               }.open();
+               new ServiceTracker<ConfigurationAdmin, ConfigurationAdmin>(bc, ConfigurationAdmin.class, null) {
+                       @Override
+                       public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> reference) {
+                               ConfigurationAdmin configurationAdmin = bc.getService(reference);
+                               deployConfig = new DeployConfig(configurationAdmin, cleanState);
+                               httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
+                               try {
+                                       Configuration[] configs = configurationAdmin
+                                                       .listConfigurations("(service.factoryPid=" + NodeConstants.NODE_REPOS_FACTORY_PID + ")");
+                                       for (Configuration config : configs) {
+                                               Object cn = config.getProperties().get(NodeConstants.CN);
+                                               log.debug("Standalone repo cn: " + cn);
+                                       }
+                               } catch (Exception e) {
+                                       throw new CmsException("Cannot initialize config", e);
+                               }
+                               return super.addingService(reference);
+                       }
+               }.open();
+       }
+
+       public void shutdown() {
+               if(nodeHttp!=null)
+                       nodeHttp.destroy();
+               if (deployConfig != null)
+                       deployConfig.save();
+       }
+
+       private void checkReadiness() {
+               if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) {
+                       availableSince = System.currentTimeMillis();
+                       long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+                       log.info("## ARGEO CMS AVAILABLE in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s ##");
+                       long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince();
+                       long initDuration = System.currentTimeMillis() - begin;
+                       if (log.isTraceEnabled())
+                               log.trace("Kernel initialization took " + initDuration + "ms");
+                       directorsCut(initDuration);
+               }
+       }
+
+       final private void directorsCut(long initDuration) {
+               // final long ms = 128l + (long) (Math.random() * 128d);
+               long ms = initDuration / 100;
+               log.info("Spend " + ms + "ms" + " reflecting on the progress brought to mankind" + " by Free Software...");
+               long beginNano = System.nanoTime();
+               try {
+                       Thread.sleep(ms, 0);
+               } catch (InterruptedException e) {
+                       // silent
+               }
+               long durationNano = System.nanoTime() - beginNano;
+               final double M = 1000d * 1000d;
+               double sleepAccuracy = ((double) durationNano) / (ms * M);
+               if (log.isDebugEnabled())
+                       log.debug("Sleep accuracy: " + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100)) + " %");
+       }
+
+       private void prepareNodeRepository(Repository deployedNodeRepository) {
+               if (availableSince != null) {
+                       throw new CmsException("Deployment is already available");
+               }
+
+               // home
                prepareDataModel(KernelUtils.openAdminSession(deployedNodeRepository));
+       }
+
+       private void prepareHomeRepository(Repository deployedRepository) {
                Hashtable<String, String> regProps = new Hashtable<String, String>();
-               regProps.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, ArgeoJcrConstants.ALIAS_HOME);
-               homeRepository = new HomeRepository(deployedNodeRepository);
+               regProps.put(NodeConstants.CN, NodeConstants.HOME);
+               regProps.put(LEGACY_JCR_REPOSITORY_ALIAS, NodeConstants.HOME);
+               homeRepository = new HomeRepository(deployedRepository);
                // register
                bc.registerService(Repository.class, homeRepository, regProps);
-}
+
+               new ServiceTracker<CallbackHandler, CallbackHandler>(bc, CallbackHandler.class, null) {
+
+                       @Override
+                       public CallbackHandler addingService(ServiceReference<CallbackHandler> reference) {
+                               NodeKeyRing nodeKeyring = new NodeKeyRing(homeRepository);
+                               CallbackHandler callbackHandler = bc.getService(reference);
+                               nodeKeyring.setDefaultCallbackHandler(callbackHandler);
+                               bc.registerService(LangUtils.names(CryptoKeyring.class, ManagedService.class), nodeKeyring,
+                                               LangUtils.dico(Constants.SERVICE_PID, NodeConstants.NODE_KEYRING_PID));
+                               return callbackHandler;
+                       }
+
+               }.open();
+       }
 
        /** Session is logged out. */
        private void prepareDataModel(Session adminSession) {
@@ -58,11 +191,8 @@ public class CmsDeployment implements NodeDeployment, ManagedService {
                        Set<String> processed = new HashSet<String>();
                        bundles: for (Bundle bundle : bc.getBundles()) {
                                BundleWiring wiring = bundle.adapt(BundleWiring.class);
-                               if (wiring == null) {
-                                       if (log.isTraceEnabled())
-                                               log.error("No wiring for " + bundle.getSymbolicName());
+                               if (wiring == null)
                                        continue bundles;
-                               }
                                processWiring(adminSession, wiring, processed);
                        }
                } finally {
@@ -85,13 +215,13 @@ public class CmsDeployment implements NodeDeployment, ManagedService {
 
        private void registerCnd(Session adminSession, BundleCapability capability, Set<String> processed) {
                Map<String, Object> attrs = capability.getAttributes();
-               String name = attrs.get(DataModelNamespace.CAPABILITY_NAME_ATTRIBUTE).toString();
+               String name = (String) attrs.get(DataModelNamespace.CAPABILITY_NAME_ATTRIBUTE);
                if (processed.contains(name)) {
                        if (log.isTraceEnabled())
                                log.trace("Data model " + name + " has already been processed");
                        return;
                }
-               String path = attrs.get(DataModelNamespace.CAPABILITY_CND_ATTRIBUTE).toString();
+               String path = (String) attrs.get(DataModelNamespace.CAPABILITY_CND_ATTRIBUTE);
                URL url = capability.getRevision().getBundle().getResource(path);
                try (Reader reader = new InputStreamReader(url.openStream())) {
                        CndImporter.registerNodeTypes(reader, adminSession, true);
@@ -102,16 +232,69 @@ public class CmsDeployment implements NodeDeployment, ManagedService {
                        throw new CmsException("Cannot import CND " + url, e);
                }
 
-               Hashtable<String, Object> properties = new Hashtable<>();
-               properties.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, name);
-               bc.registerService(Repository.class, adminSession.getRepository(), properties);
-               if (log.isDebugEnabled())
-                       log.debug("Published data model " + name);
+               if (!asBoolean((String) attrs.get(DataModelNamespace.CAPABILITY_ABSTRACT_ATTRIBUTE))) {
+                       Hashtable<String, Object> properties = new Hashtable<>();
+                       properties.put(LEGACY_JCR_REPOSITORY_ALIAS, name);
+                       properties.put(NodeConstants.CN, name);
+                       if (name.equals(NodeConstants.NODE))
+                               properties.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
+                       LocalRepository localRepository = new LocalRepository(adminSession.getRepository(), capability);
+                       bc.registerService(Repository.class, localRepository, properties);
+                       if (log.isDebugEnabled())
+                               log.debug("Published data model " + name);
+               }
+       }
+
+       private boolean asBoolean(String value) {
+               if (value == null)
+                       return false;
+               switch (value) {
+               case "true":
+                       return true;
+               case "false":
+                       return false;
+               default:
+                       throw new CmsException("Unsupported value for attribute " + DataModelNamespace.CAPABILITY_ABSTRACT_ATTRIBUTE
+                                       + ": " + value);
+               }
+       }
+
+       @Override
+       public Long getAvailableSince() {
+               return availableSince;
        }
 
-       public void setDeployedNodeRepository(Repository deployedNodeRepository) {
-               this.deployedNodeRepository = deployedNodeRepository;
+       private class RepositoryContextStc extends ServiceTracker<RepositoryContext, RepositoryContext> {
+
+               public RepositoryContextStc() {
+                       super(bc, RepositoryContext.class, null);
+               }
+
+               @Override
+               public RepositoryContext addingService(ServiceReference<RepositoryContext> reference) {
+                       RepositoryContext nodeRepo = bc.getService(reference);
+                       Object cn = reference.getProperty(NodeConstants.CN);
+                       if (cn != null) {
+                               if (cn.equals(NodeConstants.NODE)) {
+                                       prepareNodeRepository(nodeRepo.getRepository());
+                                       prepareHomeRepository(nodeRepo.getRepository());
+                                       nodeAvailable = true;
+                                       checkReadiness();
+                               } else {
+                                       // TODO standalone
+                               }
+                       }
+                       return nodeRepo;
+               }
+
+               @Override
+               public void modifiedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
+               }
+
+               @Override
+               public void removedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
+               }
+
        }
 
-       
 }