Refactor CMS UUID factory
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / runtime / CmsStateImpl.java
index d364620f56f09118a99cd36564146c1f61919a8f..2758ba9b191bf08693c37d44ff457f0240622e84 100644 (file)
@@ -3,9 +3,11 @@ package org.argeo.cms.internal.runtime;
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.Reader;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.URL;
 import java.net.UnknownHostException;
+import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -23,6 +25,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.StringJoiner;
+import java.util.TreeMap;
 import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ForkJoinPool;
@@ -35,15 +38,18 @@ import javax.security.auth.login.Configuration;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsState;
-import org.argeo.api.uuid.UuidFactory;
+import org.argeo.api.uuid.NodeIdSupplier;
+import org.argeo.api.uuid.UuidBinaryUtils;
 import org.argeo.cms.CmsDeployProperty;
 import org.argeo.cms.auth.ident.IdentClient;
+import org.argeo.cms.util.DigestUtils;
 import org.argeo.cms.util.FsUtils;
+import org.argeo.cms.util.OS;
 
 /**
  * Implementation of a {@link CmsState}, initialising the required services.
  */
-public class CmsStateImpl implements CmsState {
+public class CmsStateImpl implements CmsState, NodeIdSupplier {
        private final static CmsLog log = CmsLog.getLog(CmsStateImpl.class);
 
        // REFERENCES
@@ -52,8 +58,9 @@ public class CmsStateImpl implements CmsState {
        private UUID uuid;
 //     private final boolean cleanState;
        private String hostname;
+       private InetAddress inetAddress;
 
-       private UuidFactory uuidFactory;
+//     private UuidFactory uuidFactory;
 
        private final Map<CmsDeployProperty, String> deployPropertyDefaults;
 
@@ -91,6 +98,9 @@ public class CmsStateImpl implements CmsState {
        }
 
        public void start() {
+               Charset defaultCharset = Charset.defaultCharset();
+               if (!StandardCharsets.UTF_8.equals(defaultCharset))
+                       log.error("Default JVM charset is " + defaultCharset + " and not " + StandardCharsets.UTF_8);
                try {
                        // First init check
                        Path privateBase = getDataPath(KernelConstants.DIR_PRIVATE);
@@ -105,7 +115,8 @@ public class CmsStateImpl implements CmsState {
                        if (log.isTraceEnabled())
                                log.trace("CMS State started");
 
-                       this.uuid = uuidFactory.timeUUID();
+                       String frameworkUuid = KernelUtils.getFrameworkProp(KernelUtils.OSGI_FRAMEWORK_UUID);
+                       this.uuid = frameworkUuid != null ? UUID.fromString(frameworkUuid) : UUID.randomUUID();
 
                        // hostname
                        this.hostname = getDeployProperty(CmsDeployProperty.HOST);
@@ -114,7 +125,8 @@ public class CmsStateImpl implements CmsState {
                                final String LOCALHOST_IP = "::1";
                                ForkJoinTask<String> hostnameFJT = ForkJoinPool.commonPool().submit(() -> {
                                        try {
-                                               String hostname = InetAddress.getLocalHost().getHostName();
+                                               this.inetAddress = InetAddress.getLocalHost();
+                                               String hostname = this.inetAddress.getHostName();
                                                return hostname;
                                        } catch (UnknownHostException e) {
                                                throw new IllegalStateException("Cannot get local hostname", e);
@@ -126,6 +138,16 @@ public class CmsStateImpl implements CmsState {
                                        this.hostname = LOCALHOST_IP;
                                        log.warn("Could not get local hostname, using " + this.hostname);
                                }
+                       } else {
+                               InetAddress[] addresses = InetAddress.getAllByName(this.hostname);
+                               InetAddress selectedAddr = null;
+                               addresses: for (InetAddress addr : addresses) {
+                                       if (selectedAddr == null)
+                                               selectedAddr = addr;
+                                       if (selectedAddr instanceof Inet6Address)
+                                               break addresses;
+                               }
+                               this.inetAddress = selectedAddr;
                        }
 
                        availableSince = System.currentTimeMillis();
@@ -148,11 +170,22 @@ public class CmsStateImpl implements CmsState {
                                                }
                                        }
                                }
-                               log.debug("## CMS starting... (" + uuid + ")\n" + sb + "\n");
+                               log.debug("## CMS starting on " + hostname + " ... (" + uuid + ")\n" + sb + "\n");
+                       }
+
+                       if (log.isTraceEnabled()) {
+                               // print system properties
+                               StringJoiner sb = new StringJoiner("\n");
+                               for (Object key : new TreeMap<>(System.getProperties()).keySet()) {
+                                       sb.add(key + "=" + System.getProperty(key.toString()));
+                               }
+                               log.trace("System properties:\n" + sb + "\n");
+
                        }
 
                } catch (RuntimeException | IOException e) {
                        log.error("## FATAL: CMS state failed", e);
+                       throw new IllegalStateException(e);
                }
        }
 
@@ -168,7 +201,8 @@ public class CmsStateImpl implements CmsState {
                        try {
                                if (!Files.exists(privateDir))
                                        Files.createDirectories(privateDir);
-                               Files.setPosixFilePermissions(privateDir, posixPermissions);
+                               if (!OS.LOCAL.isMSWindows())
+                                       Files.setPosixFilePermissions(privateDir, posixPermissions);
                        } catch (IOException e) {
                                log.error("Cannot set permissions on " + privateDir, e);
                        }
@@ -184,8 +218,9 @@ public class CmsStateImpl implements CmsState {
                // explicitly load JAAS configuration
                Configuration.getConfiguration();
 
-               boolean initSsl = getDeployProperty(CmsDeployProperty.HTTPS_PORT) != null;
-               if (initSsl) {
+               boolean initCertificates = (getDeployProperty(CmsDeployProperty.HTTPS_PORT) != null)
+                               || (getDeployProperty(CmsDeployProperty.SSHD_PORT) != null);
+               if (initCertificates) {
                        initCertificates();
                }
        }
@@ -234,9 +269,6 @@ public class CmsStateImpl implements CmsState {
                                log.error("Cannot trust CA certificate", e);
                        }
                }
-
-//             if (!Files.exists(keyStorePath))
-//                     PkiUtils.createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
        }
 
        public void stop() {
@@ -244,7 +276,8 @@ public class CmsStateImpl implements CmsState {
                        log.debug("CMS stopping...  (" + this.uuid + ")");
 
                long duration = ((System.currentTimeMillis() - availableSince) / 1000) / 60;
-               log.info("## ARGEO CMS STOPPED after " + (duration / 60) + "h " + (duration % 60) + "min uptime ##");
+               log.info("## ARGEO CMS " + uuid + " STOPPED after " + (duration / 60) + "h " + (duration % 60)
+                               + "min uptime ##");
        }
 
        private void firstInit() throws IOException {
@@ -371,11 +404,87 @@ public class CmsStateImpl implements CmsState {
                return KernelUtils.getOsgiInstancePath(relativePath);
        }
 
+       @Override
+       public Path getStatePath(String relativePath) {
+               return KernelUtils.getOsgiConfigurationPath(relativePath);
+       }
+
        @Override
        public Long getAvailableSince() {
                return availableSince;
        }
 
+       /*
+        * NodeID supplier
+        */
+
+       @Override
+       public Long get() {
+               return NodeIdSupplier.toNodeIdBase(getIpBytes());
+       }
+
+       /** Returns an SHA1 digest of one of the IP addresses. */
+       protected byte[] getIpBytes() {
+//             Enumeration<NetworkInterface> netInterfaces = null;
+//             try {
+//                     netInterfaces = NetworkInterface.getNetworkInterfaces();
+//             } catch (SocketException e) {
+//                     throw new IllegalStateException(e);
+//             }
+//
+//             InetAddress selectedIpv6 = null;
+//             InetAddress selectedIpv4 = null;
+//             if (netInterfaces != null) {
+//                     netInterfaces: while (netInterfaces.hasMoreElements()) {
+//                             NetworkInterface netInterface = netInterfaces.nextElement();
+//                             byte[] hardwareAddress = null;
+//                             try {
+//                                     hardwareAddress = netInterface.getHardwareAddress();
+//                                     if (hardwareAddress != null) {
+//                                             // first IPv6
+//                                             addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+//                                                     InetAddress ip = addr.getAddress();
+//                                                     if (ip instanceof Inet6Address) {
+//                                                             Inet6Address ipv6 = (Inet6Address) ip;
+//                                                             if (ipv6.isAnyLocalAddress() || ipv6.isLinkLocalAddress() || ipv6.isLoopbackAddress())
+//                                                                     continue addr;
+//                                                             selectedIpv6 = ipv6;
+//                                                             break netInterfaces;
+//                                                     }
+//
+//                                             }
+//                                             // then IPv4
+//                                             addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+//                                                     InetAddress ip = addr.getAddress();
+//                                                     if (ip instanceof Inet4Address) {
+//                                                             Inet4Address ipv4 = (Inet4Address) ip;
+//                                                             if (ipv4.isAnyLocalAddress() || ipv4.isLinkLocalAddress() || ipv4.isLoopbackAddress())
+//                                                                     continue addr;
+//                                                             selectedIpv4 = ipv4;
+//                                                             // we keep searching for IPv6
+//                                                     }
+//
+//                                             }
+//                                     }
+//                             } catch (SocketException e) {
+//                                     throw new IllegalStateException(e);
+//                             }
+//                     }
+//             }
+//             InetAddress selectedIp = selectedIpv6 != null ? selectedIpv6 : selectedIpv4;
+               if (this.inetAddress.isLoopbackAddress()) {
+                       log.warn("No IP address found, using a random node id for UUID generation");
+                       return NodeIdSupplier.randomNodeId();
+               }
+               InetAddress selectedIp = this.inetAddress;
+               byte[] digest = DigestUtils.sha1(selectedIp.getAddress());
+               log.debug("Use IP " + selectedIp + " hashed as " + UuidBinaryUtils.toHexString(digest) + " as node id");
+               byte[] nodeId = NodeIdSupplier.toNodeIdBytes(digest, 0);
+               // marks that this is not based on MAC address
+               NodeIdSupplier.forceToNoMacAddress(nodeId, 0);
+               return nodeId;
+       }
+
        /*
         * ACCESSORS
         */
@@ -384,9 +493,9 @@ public class CmsStateImpl implements CmsState {
                return uuid;
        }
 
-       public void setUuidFactory(UuidFactory uuidFactory) {
-               this.uuidFactory = uuidFactory;
-       }
+//     public void setUuidFactory(UuidFactory uuidFactory) {
+//             this.uuidFactory = uuidFactory;
+//     }
 
        public String getHostname() {
                return hostname;
@@ -432,4 +541,5 @@ public class CmsStateImpl implements CmsState {
                // TODO make passphrase more configurable
                return new IdentClient(remoteAddr);
        }
+
 }