From 01da06d541cdb4ad614579a37be64b5de900bc20 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Tue, 5 Mar 2024 18:13:36 +0100 Subject: [PATCH] Refactor CMS UUID factory --- .../argeo/api/uuid/AbstractUuidFactory.java | 24 -- .../argeo/api/uuid/ConcurrentUuidFactory.java | 2 +- .../argeo/api/uuid/MacAddressUuidFactory.java | 2 +- .../org/argeo/api/uuid/NodeIdSupplier.java | 24 ++ .../org/argeo/cms/jetty/JettyHttpServer.java | 1 + org.argeo.cms/OSGI-INF/cmsState.xml | 8 - .../{uuidFactory.xml => cmsUuidFactory.xml} | 3 +- org.argeo.cms/bnd.bnd | 3 +- .../src/org/argeo/cms/acr/CmsUuidFactory.java | 89 ----- .../argeo/cms/internal/osgi/CmsActivator.java | 52 +-- .../cms/internal/osgi/CmsOsgiLogger.java | 365 +----------------- .../cms/internal/runtime/CmsStateImpl.java | 106 ++++- .../cms/internal/runtime/CmsUuidFactory.java | 90 +++++ .../cms/internal/runtime/KernelUtils.java | 5 +- .../src/org/argeo/cms/runtime/StaticCms.java | 18 +- .../org/argeo/api/a2/ProvisioningManager.java | 2 +- .../argeo/init/osgi/OsgiRuntimeContext.java | 37 +- 17 files changed, 277 insertions(+), 554 deletions(-) delete mode 100644 org.argeo.cms/OSGI-INF/cmsState.xml rename org.argeo.cms/OSGI-INF/{uuidFactory.xml => cmsUuidFactory.xml} (51%) delete mode 100644 org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUuidFactory.java diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java index 4f2cf3765..4486a9f8b 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java @@ -110,30 +110,6 @@ public abstract class AbstractUuidFactory implements UuidFactory { return UuidBinaryUtils.fromBytes(arr); } - /* - * SPI UTILITIES - */ - /** Guarantees that a byte array of length 6 will be returned. */ - protected static byte[] toNodeIdBytes(byte[] source, int offset) { - if (source == null) - return null; - if (offset < 0 || offset + 6 > source.length) - throw new ArrayIndexOutOfBoundsException(offset); - byte[] nodeId = new byte[6]; - System.arraycopy(source, offset, nodeId, 0, 6); - return nodeId; - } - - /** - * Force this node id to be identified as no MAC address. - * - * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.5" - */ - protected static void forceToNoMacAddress(byte[] nodeId, int offset) { - assert nodeId != null && offset < nodeId.length; - nodeId[offset] = (byte) (nodeId[offset] | 1); - } - /* * DIGEST UTILITIES */ diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java index 130a90a84..d78be0c5d 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java @@ -28,7 +28,7 @@ public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory implements T Objects.requireNonNull(nodeId); if (offset + 6 > nodeId.length) throw new IllegalArgumentException("Offset too big: " + offset); - byte[] defaultNodeId = toNodeIdBytes(nodeId, offset); + byte[] defaultNodeId = NodeIdSupplier.toNodeIdBytes(nodeId, offset); long nodeIdBase = NodeIdSupplier.toNodeIdBase(defaultNodeId); setNodeIdSupplier(() -> nodeIdBase, initialClockRange); } diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java index 31fe37831..51d68e3ef 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java @@ -52,7 +52,7 @@ public class MacAddressUuidFactory extends ConcurrentUuidFactory { } - public static byte[] hardwareAddressToNodeId(NetworkInterface nic) { + public static byte[] hardwareAddressToNodeId(NetworkInterface nic) throws IllegalStateException { try { byte[] hardwareAddress = nic.getHardwareAddress(); final int length = 6; diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java index 81d368d2c..1a3cd5673 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java @@ -25,4 +25,28 @@ public interface NodeIdSupplier extends Supplier { random.nextBytes(nodeId); return nodeId; } + + /** + * Force this node id to be identified as no MAC address. + * + * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.5" + */ + static void forceToNoMacAddress(byte[] nodeId, int offset) { + assert nodeId != null && offset < nodeId.length; + nodeId[offset] = (byte) (nodeId[offset] | 1); + } + + /* + * SPI UTILITIES + */ + /** Guarantees that a byte array of length 6 will be returned. */ + static byte[] toNodeIdBytes(byte[] source, int offset) { + if (source == null) + return null; + if (offset < 0 || offset + 6 > source.length) + throw new ArrayIndexOutOfBoundsException(offset); + byte[] nodeId = new byte[6]; + System.arraycopy(source, offset, nodeId, 0, 6); + return nodeId; + } } diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java index 9d35dadb5..4e91ea41c 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java @@ -230,6 +230,7 @@ public class JettyHttpServer extends HttpsServer { server.stop(); // TODO delete temp dir started = false; + log.debug(() -> "Stopped Jetty server"); } catch (Exception e) { log.error("Cannot stop Jetty HTTP server", e); } diff --git a/org.argeo.cms/OSGI-INF/cmsState.xml b/org.argeo.cms/OSGI-INF/cmsState.xml deleted file mode 100644 index 71dc6d4db..000000000 --- a/org.argeo.cms/OSGI-INF/cmsState.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/org.argeo.cms/OSGI-INF/uuidFactory.xml b/org.argeo.cms/OSGI-INF/cmsUuidFactory.xml similarity index 51% rename from org.argeo.cms/OSGI-INF/uuidFactory.xml rename to org.argeo.cms/OSGI-INF/cmsUuidFactory.xml index c1ad6f8a9..d55970a11 100644 --- a/org.argeo.cms/OSGI-INF/uuidFactory.xml +++ b/org.argeo.cms/OSGI-INF/cmsUuidFactory.xml @@ -1,7 +1,8 @@ - + + diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index 01443b5e8..3807af9d1 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -6,9 +6,8 @@ org.osgi.*;version=0.0.0,\ Service-Component:\ OSGI-INF/cmsOsgiLogger.xml,\ -OSGI-INF/uuidFactory.xml,\ +OSGI-INF/cmsUuidFactory.xml,\ OSGI-INF/cmsEventBus.xml,\ -OSGI-INF/cmsState.xml,\ OSGI-INF/transactionManager.xml,\ OSGI-INF/cmsUserAdmin.xml,\ OSGI-INF/cmsUserManager.xml,\ diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java deleted file mode 100644 index b61e35dbc..000000000 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.argeo.cms.acr; - -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InterfaceAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.BitSet; -import java.util.Enumeration; - -import org.argeo.api.cms.CmsLog; -import org.argeo.api.uuid.ConcurrentUuidFactory; -import org.argeo.api.uuid.NodeIdSupplier; -import org.argeo.api.uuid.UuidBinaryUtils; - -public class CmsUuidFactory extends ConcurrentUuidFactory { - private final static CmsLog log = CmsLog.getLog(CmsUuidFactory.class); - - public CmsUuidFactory(byte[] nodeId) { - super(0, nodeId); - assert createTimeUUID().node() == BitSet.valueOf(toNodeIdBytes(nodeId, 0)).toLongArray()[0]; - } - - public CmsUuidFactory() { - this(getIpBytes()); - } - - /** Returns an SHA1 digest of one of the IP addresses. */ - protected static byte[] getIpBytes() { - Enumeration netInterfaces = null; - try { - netInterfaces = NetworkInterface.getNetworkInterfaces(); - } catch (SocketException e) { - throw new IllegalStateException(e); - } - if (netInterfaces == null) - throw new IllegalStateException("No interfaces"); - - InetAddress selectedIpv6 = null; - InetAddress selectedIpv4 = 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; - } - - } - } - } catch (SocketException e) { - throw new IllegalStateException(e); - } - } - InetAddress selectedIp = selectedIpv6 != null ? selectedIpv6 : selectedIpv4; - if (selectedIp == null) { - log.warn("No IP address found, using a random node id for UUID generation"); - return NodeIdSupplier.randomNodeId(); - } - byte[] digest = sha1(selectedIp.getAddress()); - log.debug("Use IP " + selectedIp + " hashed as " + UuidBinaryUtils.toHexString(digest) + " as node id"); - byte[] nodeId = toNodeIdBytes(digest, 0); - // marks that this is not based on MAC address - forceToNoMacAddress(nodeId, 0); - return nodeId; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java index b09956203..fe7c80d3b 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java @@ -1,9 +1,10 @@ package org.argeo.cms.internal.osgi; import java.security.AllPermission; -import java.util.Dictionary; -import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsState; +import org.argeo.api.uuid.NodeIdSupplier; +import org.argeo.cms.internal.runtime.CmsStateImpl; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; @@ -19,7 +20,7 @@ import org.osgi.service.permissionadmin.PermissionInfo; * bundle (and only it) */ public class CmsActivator implements BundleActivator { - private final static CmsLog log = CmsLog.getLog(CmsActivator.class); +// private final static CmsLog log = CmsLog.getLog(CmsActivator.class); // TODO make it configurable private boolean hardened = false; @@ -30,13 +31,6 @@ public class CmsActivator implements BundleActivator { } void destroy() { - try { - bundleContext = null; -// this.logReaderService = null; - } catch (Exception e) { - log.error("CMS activator shutdown failed", e); - } - new GogoShellKiller().start(); } @@ -76,14 +70,14 @@ public class CmsActivator implements BundleActivator { } - public static void registerService(Class clss, T service, Dictionary properties) { - if (bundleContext != null) { - bundleContext.registerService(clss, service, properties); - } - - } - - public static T getService(Class clss) { +// static void registerService(Class clss, T service, Dictionary properties) { +// if (bundleContext != null) { +// bundleContext.registerService(clss, service, properties); +// } +// +// } +// + static T getService(Class clss) { if (bundleContext != null) { return bundleContext.getService(bundleContext.getServiceReference(clss)); } else { @@ -98,20 +92,32 @@ public class CmsActivator implements BundleActivator { @Override public void start(BundleContext bc) throws Exception { bundleContext = bc; - + CmsStateImpl cmsState = new CmsStateImpl(); + cmsState.start(); + bundleContext.registerService(new String[] { CmsState.class.getName(), NodeIdSupplier.class.getName() }, + cmsState, null); init(); } @Override public void stop(BundleContext bc) throws Exception { - - destroy(); - bundleContext = null; + try { + destroy(); + CmsStateImpl cmsState = (CmsStateImpl) getService(CmsState.class); + cmsState.stop(); + } finally { + bundleContext = null; + } } - public static BundleContext getBundleContext() { + static BundleContext getBundleContext() { return bundleContext; } + public static String getFrameworkProperty(String key) { + if (bundleContext == null) + return null; + return getBundleContext().getProperty(key); + } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java index 85f045bae..405fa46f4 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java @@ -19,38 +19,8 @@ import org.osgi.service.log.LogReaderService; public class CmsOsgiLogger implements LogListener { private final static String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern"; private final static String CONTEXT_NAME_PROP = "contextName"; - - private LogReaderService logReaderService; - -// /** Internal debug for development purposes. */ -// private static Boolean debug = false; - -// private Boolean disabled = false; -// -// private String level = null; - -// private Level log4jLevel = null; - -// private Properties configuration; - -// private AppenderImpl appender; -// private BlockingQueue events; -// private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); - -// private Integer maxLastEventsCount = 10 * 1000; -// -// /** Marker to prevent stack overflow */ -// private ThreadLocal dispatching = new ThreadLocal() { -// -// @Override -// protected Boolean initialValue() { -// return false; -// } -// }; - -// public CmsOsgiLogger(LogReaderService lrs) { -// } + private LogReaderService logReaderService; public void start() { if (logReaderService != null) { @@ -58,51 +28,10 @@ public class CmsOsgiLogger implements LogListener { while (logEntries.hasMoreElements()) logged(logEntries.nextElement()); logReaderService.addLogListener(this); - - // configure log4j watcher -// String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration"); -// if (log4jConfiguration != null && log4jConfiguration.startsWith("file:")) { -// if (log4jConfiguration.contains("..")) { -// if (log4jConfiguration.startsWith("file://")) -// log4jConfiguration = log4jConfiguration.substring("file://".length()); -// else if (log4jConfiguration.startsWith("file:")) -// log4jConfiguration = log4jConfiguration.substring("file:".length()); -// } -// try { -// Path log4jconfigPath; -// if (log4jConfiguration.startsWith("file:")) -// log4jconfigPath = Paths.get(new URI(log4jConfiguration)); -// else -// log4jconfigPath = Paths.get(log4jConfiguration); -// Thread log4jConfWatcher = new Log4jConfWatcherThread(log4jconfigPath); -// log4jConfWatcher.start(); -// } catch (Exception e) { -// stdErr("Badly formatted log4j configuration URI " + log4jConfiguration + ": " + e.getMessage()); -// } -// } } -// try { -//// events = new LinkedBlockingQueue(); -//// -//// // if (layout != null) -//// // setLayout(layout); -//// // else -//// // setLayout(new PatternLayout(pattern)); -////// appender = new AppenderImpl(); -//// reloadConfiguration(); -////// Logger.getRootLogger().addAppender(appender); -//// -//// logDispatcherThread = new LogDispatcherThread(); -//// logDispatcherThread.start(); -// } catch (Exception e) { -// throw new IllegalStateException("Cannot initialize log4j"); -// } } public void stop() throws Exception { -// events.clear(); -// events = null; -// logDispatcherThread.interrupt(); logReaderService.removeLogListener(this); } @@ -216,296 +145,4 @@ public class CmsOsgiLogger implements LogListener { this.logReaderService = logReaderService; } - - // - // ARGEO LOGGER - // - -// public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) { -// String username = CurrentUser.getUsername(); -// if (username == null) -// throw new IllegalStateException("Only authenticated users can register a log listener"); -// -// if (!userListeners.containsKey(username)) { -// List lst = Collections.synchronizedList(new ArrayList()); -// userListeners.put(username, lst); -// } -// userListeners.get(username).add(listener); -// List lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents); -// for (LogEvent evt : lastEvents) -// dispatchEvent(listener, evt); -// } -// -// public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents, -// boolean everything) { -// if (everything) -// everythingListeners.add(listener); -// else -// allUsersListeners.add(listener); -// List lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents); -// for (LogEvent evt : lastEvents) -// if (everything || evt.getUsername() != null) -// dispatchEvent(listener, evt); -// } -// -// public synchronized void unregister(ArgeoLogListener listener) { -// String username = CurrentUser.getUsername(); -// if (username == null)// FIXME -// return; -// if (!userListeners.containsKey(username)) -// throw new IllegalStateException("No user listeners " + listener + " registered for user " + username); -// if (!userListeners.get(username).contains(listener)) -// throw new IllegalStateException("No user listeners " + listener + " registered for user " + username); -// userListeners.get(username).remove(listener); -// if (userListeners.get(username).isEmpty()) -// userListeners.remove(username); -// -// } -// -// public synchronized void unregisterForAll(ArgeoLogListener listener) { -// everythingListeners.remove(listener); -// allUsersListeners.remove(listener); -// } - -// /** For development purpose, since using regular logging is not easy here */ -// private static void stdOut(Object obj) { -// System.out.println(obj); -// } -// -// private static void stdErr(Object obj) { -// System.err.println(obj); -// } -// -// private static void debug(Object obj) { -// if (debug) -// System.out.println(obj); -// } -// -// private static boolean isInternalDebugEnabled() { -// return debug; -// } - - // public void setPattern(String pattern) { - // this.pattern = pattern; - // } - -// public void setDisabled(Boolean disabled) { -// this.disabled = disabled; -// } -// -// public void setLevel(String level) { -// this.level = level; -// } - -// public void setConfiguration(Properties configuration) { -// this.configuration = configuration; -// } -// -// public void updateConfiguration(Properties configuration) { -// setConfiguration(configuration); -// reloadConfiguration(); -// } -// -// public Properties getConfiguration() { -// return configuration; -// } -// -// /** -// * Reloads configuration (if the configuration {@link Properties} is set) -// */ -// protected void reloadConfiguration() { -// if (configuration != null) { -//// LogManager.resetConfiguration(); -//// PropertyConfigurator.configure(configuration); -// } -// } - -// protected synchronized void processLoggingEvent(LogEvent event) { -// if (disabled) -// return; -// -// if (dispatching.get()) -// return; -// -// if (level != null && !level.trim().equals("")) { -//// if (log4jLevel == null || !log4jLevel.toString().equals(level)) -//// try { -//// log4jLevel = Level.toLevel(level); -//// } catch (Exception e) { -//// System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null."); -//// e.printStackTrace(); -//// level = null; -//// } -//// -//// if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) { -//// return; -//// } -// } -// -//// try { -//// // admin listeners -//// Iterator everythingIt = everythingListeners.iterator(); -//// while (everythingIt.hasNext()) -//// dispatchEvent(everythingIt.next(), event); -//// -//// if (event.getUsername() != null) { -//// Iterator allUsersIt = allUsersListeners.iterator(); -//// while (allUsersIt.hasNext()) -//// dispatchEvent(allUsersIt.next(), event); -//// -//// if (userListeners.containsKey(event.getUsername())) { -//// Iterator userIt = userListeners.get(event.getUsername()).iterator(); -//// while (userIt.hasNext()) -//// dispatchEvent(userIt.next(), event); -//// } -//// } -//// } catch (Exception e) { -//// stdOut("Cannot process logging event"); -//// e.printStackTrace(); -//// } -// } - -// protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) { -//// LoggingEvent event = evt.getLoggingEvent(); -//// logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(), -//// event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep()); -// } - -// private class AppenderImpl { // extends AppenderSkeleton { -// public boolean requiresLayout() { -// return false; -// } -// -// public void close() { -// } -// -//// @Override -//// protected void append(LoggingEvent event) { -//// if (events != null) { -//// try { -//// String username = CurrentUser.getUsername(); -//// events.put(new LogEvent(username, event)); -//// } catch (InterruptedException e) { -//// // silent -//// } -//// } -//// } -// -// } - -// private class LogDispatcherThread extends Thread { -// /** encapsulated in order to simplify concurrency management */ -// private LinkedList lastEvents = new LinkedList(); -// -// public LogDispatcherThread() { -// super("Argeo Logging Dispatcher Thread"); -// } -// -// public void run() { -// while (events != null) { -// try { -// LogEvent loggingEvent = events.take(); -// processLoggingEvent(loggingEvent); -// addLastEvent(loggingEvent); -// } catch (InterruptedException e) { -// if (events == null) -// return; -// } -// } -// } -// -// protected synchronized void addLastEvent(LogEvent loggingEvent) { -// if (lastEvents.size() >= maxLastEventsCount) -// lastEvents.poll(); -// lastEvents.add(loggingEvent); -// } -// -// public synchronized List getLastEvents(String username, Integer maxCount) { -// LinkedList evts = new LinkedList(); -// ListIterator it = lastEvents.listIterator(lastEvents.size()); -// int count = 0; -// while (it.hasPrevious() && (count < maxCount)) { -// LogEvent evt = it.previous(); -// if (username == null || username.equals(evt.getUsername())) { -// evts.push(evt); -// count++; -// } -// } -// return evts; -// } -// } - -// private class LogEvent { -// private final String username; -//// private final LoggingEvent loggingEvent; -// -// public LogEvent(String username) { -// super(); -// this.username = username; -//// this.loggingEvent = loggingEvent; -// } -// -//// @Override -//// public int hashCode() { -//// return loggingEvent.hashCode(); -//// } -//// -//// @Override -//// public boolean equals(Object obj) { -//// return loggingEvent.equals(obj); -//// } -//// -//// @Override -//// public String toString() { -//// return username + "@ " + loggingEvent.toString(); -//// } -// -// public String getUsername() { -// return username; -// } -// -//// public LoggingEvent getLoggingEvent() { -//// return loggingEvent; -//// } -// -// } -// -// private class Log4jConfWatcherThread extends Thread { -// private Path log4jConfigurationPath; -// -// public Log4jConfWatcherThread(Path log4jConfigurationPath) { -// super("Log4j Configuration Watcher"); -// try { -// this.log4jConfigurationPath = log4jConfigurationPath.toRealPath(); -// } catch (IOException e) { -// this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath(); -// stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage()); -// } -// } -// -// public void run() { -// Path parentDir = log4jConfigurationPath.getParent(); -// try (final WatchService watchService = FileSystems.getDefault().newWatchService()) { -// parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); -// WatchKey wk; -// watching: while ((wk = watchService.take()) != null) { -// for (WatchEvent event : wk.pollEvents()) { -// final Path changed = (Path) event.context(); -// if (log4jConfigurationPath.equals(parentDir.resolve(changed))) { -// if (isInternalDebugEnabled()) -// debug(log4jConfigurationPath + " has changed, reloading."); -//// PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL()); -// } -// } -// // reset the key -// boolean valid = wk.reset(); -// if (!valid) { -// break watching; -// } -// } -// } catch (IOException | InterruptedException e) { -// stdErr("Log4j configuration watcher failed: " + e.getMessage()); -// } -// } -// } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java index ee7f06340..2758ba9b1 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java @@ -3,6 +3,7 @@ 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; @@ -37,16 +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 @@ -55,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 deployPropertyDefaults; @@ -112,7 +116,7 @@ public class CmsStateImpl implements CmsState { log.trace("CMS State started"); String frameworkUuid = KernelUtils.getFrameworkProp(KernelUtils.OSGI_FRAMEWORK_UUID); - this.uuid = frameworkUuid != null ? UUID.fromString(frameworkUuid) : uuidFactory.timeUUID(); + this.uuid = frameworkUuid != null ? UUID.fromString(frameworkUuid) : UUID.randomUUID(); // hostname this.hostname = getDeployProperty(CmsDeployProperty.HOST); @@ -121,7 +125,8 @@ public class CmsStateImpl implements CmsState { final String LOCALHOST_IP = "::1"; ForkJoinTask 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); @@ -133,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(); @@ -155,7 +170,7 @@ 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()) { @@ -170,6 +185,7 @@ public class CmsStateImpl implements CmsState { } catch (RuntimeException | IOException e) { log.error("## FATAL: CMS state failed", e); + throw new IllegalStateException(e); } } @@ -398,6 +414,77 @@ public class CmsStateImpl implements CmsState { 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 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 */ @@ -406,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; @@ -454,4 +541,5 @@ public class CmsStateImpl implements CmsState { // TODO make passphrase more configurable return new IdentClient(remoteAddr); } + } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUuidFactory.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUuidFactory.java new file mode 100644 index 000000000..407783806 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUuidFactory.java @@ -0,0 +1,90 @@ +package org.argeo.cms.internal.runtime; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.BitSet; +import java.util.Enumeration; + +import org.argeo.api.cms.CmsLog; +import org.argeo.api.uuid.ConcurrentUuidFactory; +import org.argeo.api.uuid.NodeIdSupplier; +import org.argeo.api.uuid.UuidBinaryUtils; + +@Deprecated +class CmsUuidFactory extends ConcurrentUuidFactory { + private final static CmsLog log = CmsLog.getLog(CmsUuidFactory.class); + + public CmsUuidFactory(byte[] nodeId) { + super(0, nodeId); + assert createTimeUUID().node() == BitSet.valueOf(NodeIdSupplier.toNodeIdBytes(nodeId, 0)).toLongArray()[0]; + } + + public CmsUuidFactory() { + this(getIpBytes()); + } + + /** Returns an SHA1 digest of one of the IP addresses. */ + protected static byte[] getIpBytes() { + Enumeration 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 (selectedIp == null) { + log.warn("No IP address found, using a random node id for UUID generation"); + return NodeIdSupplier.randomNodeId(); + } + byte[] digest = 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; + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java index 3797eb913..db33ff9d4 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java @@ -96,9 +96,8 @@ class KernelUtils implements KernelConstants { static String getFrameworkProp(String key, String def) { String value; - if (CmsActivator.getBundleContext() != null) - value = CmsActivator.getBundleContext().getProperty(key); - else + value = CmsActivator.getFrameworkProperty(key); + if (value == null) value = System.getProperty(key); if (value == null) return def; diff --git a/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java index 76775fed8..1b127c89d 100644 --- a/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java +++ b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java @@ -16,8 +16,9 @@ import org.argeo.api.cms.transaction.WorkTransaction; import org.argeo.api.register.Component; import org.argeo.api.register.ComponentRegister; import org.argeo.api.register.SimpleRegister; +import org.argeo.api.uuid.ConcurrentUuidFactory; +import org.argeo.api.uuid.NodeIdSupplier; import org.argeo.api.uuid.UuidFactory; -import org.argeo.cms.acr.CmsUuidFactory; import org.argeo.cms.internal.runtime.CmsContextImpl; import org.argeo.cms.internal.runtime.CmsDeploymentImpl; import org.argeo.cms.internal.runtime.CmsStateImpl; @@ -36,19 +37,20 @@ public class StaticCms { private CompletableFuture stopped = new CompletableFuture(); public void start() { - // UID factory - CmsUuidFactory uuidFactory = new CmsUuidFactory(); - Component uuidFactoryC = new Component.Builder<>(uuidFactory) // - .addType(UuidFactory.class) // - .build(register); - // CMS State CmsStateImpl cmsState = new CmsStateImpl(); Component cmsStateC = new Component.Builder<>(cmsState) // .addType(CmsState.class) // + .addType(NodeIdSupplier.class) // .addActivation(cmsState::start) // .addDeactivation(cmsState::stop) // - .addDependency(uuidFactoryC.getType(UuidFactory.class), cmsState::setUuidFactory, null) // + .build(register); + + // UID factory + ConcurrentUuidFactory uuidFactory = new ConcurrentUuidFactory(); + Component uuidFactoryC = new Component.Builder<>(uuidFactory) // + .addType(UuidFactory.class) // + .addDependency(cmsStateC.getType(NodeIdSupplier.class), uuidFactory::setNodeIdSupplier, null) // .build(register); // Transaction manager diff --git a/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java b/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java index 7a17932df..af22787b1 100644 --- a/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java +++ b/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java @@ -99,7 +99,7 @@ public class ProvisioningManager { includes, excludes); source.load(); addSource(source); - logger.log(INFO, () -> "Registered " + uri + " as source"); + logger.log(DEBUG, () -> "Registered " + uri + " as source"); // OS specific / native String localRelPath = A2Contribution.localOsArchRelativePath(); diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java index b7079f60e..ecfeca74e 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java @@ -95,7 +95,13 @@ public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable { new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.publisher"))); } OsgiBoot osgiBoot = new OsgiBoot(bundleContext); - osgiBoot.bootstrap(config); + String frameworkUuuid = bundleContext.getProperty(Constants.FRAMEWORK_UUID); + new Thread("OSGi boot framework " + frameworkUuuid) { + @Override + public void run() { + osgiBoot.bootstrap(config); + } + }.start(); } public void update() { @@ -151,28 +157,19 @@ public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable { public void close() throws Exception { if (framework == null) return; -// Bundle scrBundle = osgiBoot.getBundlesBySymbolicName().get(); -// if (scrBundle != null && scrBundle.getState() > Bundle.RESOLVED) { -// scrBundle.stop(); -// while (!(scrBundle.getState() <= Bundle.RESOLVED)) { -// Thread.sleep(500); + // TODO make shutdown of dynamic service more robust +// for (Bundle scrBundle : framework.getBundleContext().getBundles()) { +// if (scrBundle.getSymbolicName().equals(SYMBOLIC_NAME_FELIX_SCR)) { +// if (scrBundle.getState() > Bundle.RESOLVED) { +// scrBundle.stop(); +// while (!(scrBundle.getState() <= Bundle.RESOLVED)) { +// Thread.sleep(100); +// } +// Thread.sleep(500); +// } // } -// Thread.sleep(1000); // } - // TODO make shutdown of dynamic service more robust - for (Bundle scrBundle : framework.getBundleContext().getBundles()) { - if (scrBundle.getSymbolicName().equals(SYMBOLIC_NAME_FELIX_SCR)) { - if (scrBundle.getState() > Bundle.RESOLVED) { - scrBundle.stop(); - while (!(scrBundle.getState() <= Bundle.RESOLVED)) { - Thread.sleep(100); - } - Thread.sleep(100); - } - } - } - stop(); waitForStop(CLOSE_TIMEOUT); framework = null; -- 2.30.2