You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by gt...@apache.org on 2022/10/13 17:23:17 UTC

[activemq-artemis] 02/02: ARTEMIS-4028 - add alder32 checksum to the status of properties files read by the broker

This is an automated email from the ASF dual-hosted git repository.

gtully pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git

commit 3b981b3920dc2214e943d9de81bd991fb7182eb6
Author: Gary Tully <ga...@gmail.com>
AuthorDate: Tue Oct 4 14:04:41 2022 +0100

    ARTEMIS-4028 - add alder32 checksum to the status of properties files read by the broker
---
 .../FluentPropertyBeanIntrospectorWithIgnores.java |  2 +-
 .../apache/activemq/artemis/api/core/JsonUtil.java | 15 ++++
 .../activemq/artemis/api/core/JsonUtilTest.java    | 19 +++++
 .../core/config/impl/ConfigurationImpl.java        | 93 +++++++++++-----------
 .../core/server/impl/ActiveMQServerImpl.java       |  6 +-
 .../artemis/core/server/impl/ServerStatus.java     | 58 +++++++++-----
 .../core/security/jaas/ReloadableProperties.java   | 34 +++++++-
 .../core/config/impl/ConfigurationImplTest.java    |  1 +
 .../security/jaas/PropertiesLoginModuleTest.java   | 28 +++++++
 9 files changed, 182 insertions(+), 74 deletions(-)

diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/uri/FluentPropertyBeanIntrospectorWithIgnores.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/uri/FluentPropertyBeanIntrospectorWithIgnores.java
index ca0b99b0a9..e21dddcf09 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/uri/FluentPropertyBeanIntrospectorWithIgnores.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/uri/FluentPropertyBeanIntrospectorWithIgnores.java
@@ -63,7 +63,7 @@ public class FluentPropertyBeanIntrospectorWithIgnores extends FluentPropertyBea
                   pd.setWriteMethod(m);
                }
             } catch (IntrospectionException e) {
-               logger.debug(e.getMessage(), e);
+               logger.trace("error for property named {}", propertyName, e);
             }
          }
       }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java
index bfa7bbce31..8d193412d1 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java
@@ -409,6 +409,21 @@ public final class JsonUtil {
       return jsonObjectBuilder.build();
    }
 
+   // component path, may contain x/y/z as a pointer to a nested object
+   public static JsonObjectBuilder objectBuilderWithValueAtPath(String componentPath, JsonValue componentStatus) {
+      JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder();
+      String[] nestedComponents = componentPath.split("/", 0);
+      // may need to nest this status in objects
+      for (int i = nestedComponents.length - 1; i > 0; i--) {
+         JsonObjectBuilder nestedBuilder = JsonLoader.createObjectBuilder();
+         nestedBuilder.add(nestedComponents[i], componentStatus);
+         componentStatus = nestedBuilder.build();
+      }
+      jsonObjectBuilder.add(nestedComponents[0], componentStatus);
+      return jsonObjectBuilder;
+   }
+
+
    private static class NullableJsonString implements JsonValue, JsonString {
 
       private final String value;
diff --git a/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/JsonUtilTest.java b/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/JsonUtilTest.java
index 7c11ad4534..b2ff4394e4 100644
--- a/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/JsonUtilTest.java
+++ b/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/JsonUtilTest.java
@@ -212,4 +212,23 @@ public class JsonUtilTest {
 
       Assert.assertEquals(sourceTwo, JsonUtil.mergeAndUpdate(sourceOne, sourceTwo));
    }
+
+   @Test
+   public void testBuilderWithValueAtPath() {
+
+      JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder();
+      JsonUtil.addToObject("x", "y", jsonObjectBuilder);
+      JsonObject target = jsonObjectBuilder.build();
+
+      JsonObjectBuilder nested = JsonUtil.objectBuilderWithValueAtPath("a/b/c", target);
+      JsonObject inserted = nested.build();
+      Assert.assertTrue(inserted.containsKey("a"));
+
+      Assert.assertEquals(target, inserted.getJsonObject("a").getJsonObject("b").getJsonObject("c"));
+
+      nested = JsonUtil.objectBuilderWithValueAtPath("c", target);
+      inserted = nested.build();
+      Assert.assertTrue(inserted.containsKey("c"));
+      Assert.assertEquals(target, inserted.getJsonObject("c"));
+   }
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
index a7116a1e54..c9d349ed1b 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
@@ -34,6 +34,7 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.URI;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.security.AccessController;
 import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
@@ -123,6 +124,9 @@ import org.apache.commons.beanutils.expression.DefaultResolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import java.lang.invoke.MethodHandles;
+import java.util.zip.Adler32;
+import java.util.zip.Checksum;
+
 import org.apache.commons.beanutils.expression.Resolver;
 
 public class ConfigurationImpl implements Configuration, Serializable {
@@ -411,7 +415,8 @@ public class ConfigurationImpl implements Configuration, Serializable {
     * Parent folder for all data folders.
     */
    private File artemisInstance;
-   private transient volatile JsonObject status = JsonLoader.createObjectBuilder().build();
+   private transient JsonObject status = JsonLoader.createObjectBuilder().build();
+   private final transient Checksum checksum = new Adler32();
 
    @Override
    public String getJournalRetentionDirectory() {
@@ -501,15 +506,16 @@ public class ConfigurationImpl implements Configuration, Serializable {
                         try (FileInputStream fileInputStream = new FileInputStream(new File(dir, fileName)); BufferedInputStream reader = new BufferedInputStream(fileInputStream)) {
                            brokerProperties.clear();
                            brokerProperties.load(reader);
-                           parsePrefixedProperties(brokerProperties, null);
+                           parsePrefixedProperties(fileName, brokerProperties, null);
                         }
                      }
                   }
                }
             } else {
-               try (FileInputStream fileInputStream = new FileInputStream(fileUrl); BufferedInputStream reader = new BufferedInputStream(fileInputStream)) {
+               File file = new File(fileUrl);
+               try (FileInputStream fileInputStream = new FileInputStream(file); BufferedInputStream reader = new BufferedInputStream(fileInputStream)) {
                   brokerProperties.load(reader);
-                  parsePrefixedProperties(brokerProperties, null);
+                  parsePrefixedProperties(file.getName(), brokerProperties, null);
                }
             }
          }
@@ -519,9 +525,14 @@ public class ConfigurationImpl implements Configuration, Serializable {
    }
 
    public void parsePrefixedProperties(Properties properties, String prefix) throws Exception {
-      Map<String, Object> beanProperties = new LinkedHashMap<>();
+      parsePrefixedProperties("system", properties, prefix);
+   }
 
+   public void parsePrefixedProperties(String name, Properties properties, String prefix) throws Exception {
+      Map<String, Object> beanProperties = new LinkedHashMap<>();
+      long alder32Hash = 0;
       synchronized (properties) {
+         checksum.reset();
          String key = null;
          for (Map.Entry<Object, Object> entry : properties.entrySet()) {
             key = entry.getKey().toString();
@@ -531,53 +542,37 @@ public class ConfigurationImpl implements Configuration, Serializable {
                }
                key = entry.getKey().toString().substring(prefix.length());
             }
-            String value = XMLUtil.replaceSystemPropsInString(entry.getValue().toString());
+            String value = entry.getValue().toString();
+
+            checksum.update(key.getBytes(StandardCharsets.UTF_8));
+            checksum.update('=');
+            checksum.update(value.getBytes(StandardCharsets.UTF_8));
+
+            value = XMLUtil.replaceSystemPropsInString(value);
             value = PasswordMaskingUtil.resolveMask(isMaskPassword(), value, getPasswordCodec());
             key = XMLUtil.replaceSystemPropsInString(key);
             logger.debug("Property config, {}={}", key, value);
             beanProperties.put(key, value);
          }
+         alder32Hash = checksum.getValue();
       }
+      updateReadPropertiesStatus(name, alder32Hash);
 
       if (!beanProperties.isEmpty()) {
-         populateWithProperties(beanProperties);
+         populateWithProperties(name, beanProperties);
       }
    }
 
-   public void populateWithProperties(Map<String, Object> beanProperties) throws InvocationTargetException, IllegalAccessException {
+   public void populateWithProperties(final String propsId, Map<String, Object> beanProperties) throws InvocationTargetException, IllegalAccessException {
       CollectionAutoFillPropertiesUtil autoFillCollections = new CollectionAutoFillPropertiesUtil();
       BeanUtilsBean beanUtils = new BeanUtilsBean(new ConvertUtilsBean(), autoFillCollections) {
          // override to treat missing properties as errors, not skip as the default impl does
          @Override
          public void setProperty(final Object bean, String name, final Object value) throws InvocationTargetException, IllegalAccessException {
             {
-               if (logger.isTraceEnabled()) {
-                  final StringBuilder sb = new StringBuilder("  setProperty(");
-                  sb.append(bean);
-                  sb.append(", ");
-                  sb.append(name);
-                  sb.append(", ");
-                  if (value == null) {
-                     sb.append("<NULL>");
-                  } else if (value instanceof String) {
-                     sb.append((String) value);
-                  } else if (value instanceof String[]) {
-                     final String[] values = (String[]) value;
-                     sb.append('[');
-                     for (int i = 0; i < values.length; i++) {
-                        if (i > 0) {
-                           sb.append(',');
-                        }
-                        sb.append(values[i]);
-                     }
-                     sb.append(']');
-                  } else {
-                     sb.append(value.toString());
-                  }
-                  sb.append(')');
-                  logger.trace(sb.toString());
+               if (logger.isDebugEnabled()) {
+                  logger.debug("setProperty on {}, name: {}, value: {}", bean.getClass(), name, value);
                }
-
                // Resolve any nested expression to get the actual target bean
                Object target = bean;
                final Resolver resolver = getPropertyUtils().getResolver();
@@ -592,10 +587,7 @@ public class ConfigurationImpl implements Configuration, Serializable {
                      throw new InvocationTargetException(e, "No getter for property:" + name + ", on: " + bean);
                   }
                }
-               if (logger.isTraceEnabled()) {
-                  logger.trace("    Target bean = " + target);
-                  logger.trace("    Target name = " + name);
-               }
+               logger.trace("resolved target, bean: {}, name: {}", target.getClass(), name);
 
                // Declare local variables we will require
                final String propName = resolver.getProperty(name); // Simple name of target property
@@ -746,7 +738,7 @@ public class ConfigurationImpl implements Configuration, Serializable {
 
       BeanSupport.customise(beanUtils);
 
-      logger.debug("populate:" + this + ", " + beanProperties);
+      logger.trace("populate: bean: {} with {}", this, beanProperties);
 
       HashMap<String, String> errors = new HashMap<>();
       // Loop through the property name/value pairs to be set
@@ -757,7 +749,7 @@ public class ConfigurationImpl implements Configuration, Serializable {
             // Perform the assignment for this property
             beanUtils.setProperty(this, name, entry.getValue());
          } catch (InvocationTargetException invocationTargetException) {
-            logger.trace("failed to populate property key: " + name, invocationTargetException);
+            logger.trace("failed to populate property with key: {}", name, invocationTargetException);
             Throwable toLog = invocationTargetException;
             if (invocationTargetException.getCause() != null) {
                toLog = invocationTargetException.getCause();
@@ -768,23 +760,28 @@ public class ConfigurationImpl implements Configuration, Serializable {
             trackError(errors, entry, oops);
          }
       }
-      updateStatus(errors);
+      updateApplyStatus(propsId, errors);
    }
 
    private void trackError(HashMap<String, String> errors, Map.Entry<String,?> entry, Throwable oops) {
-      logger.debug("failed to populate property entry(" + entry.toString() + "), reason: " + oops.getLocalizedMessage(), oops);
+      logger.debug("failed to populate property entry({}), reason: {}", entry, oops);
       errors.put(entry.toString(), oops.getLocalizedMessage());
    }
 
-   private void updateStatus(HashMap<String, String> errors) {
-
-      JsonArrayBuilder errorsObjectArray = JsonLoader.createArrayBuilder();
+   private synchronized void updateApplyStatus(String propsId, HashMap<String, String> errors) {
+      JsonArrayBuilder errorsObjectArrayBuilder = JsonLoader.createArrayBuilder();
       for (Map.Entry<String, String> entry : errors.entrySet()) {
-         errorsObjectArray.add(JsonLoader.createObjectBuilder().add("value", entry.getKey()).add("reason", entry.getValue()));
+         errorsObjectArrayBuilder.add(JsonLoader.createObjectBuilder().add("value", entry.getKey()).add("reason", entry.getValue()));
       }
-      JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder();
-      jsonObjectBuilder.add("properties",  JsonLoader.createObjectBuilder().add("errors", errorsObjectArray));
+      JsonObjectBuilder jsonObjectBuilder =
+         JsonUtil.objectBuilderWithValueAtPath("properties/" + propsId + "/errors", errorsObjectArrayBuilder.build());
+      status = JsonUtil.mergeAndUpdate(status, jsonObjectBuilder.build());
+   }
 
+   private synchronized void updateReadPropertiesStatus(String propsId, long alder32Hash) {
+      JsonObjectBuilder propertiesReadStatusBuilder = JsonLoader.createObjectBuilder();
+      propertiesReadStatusBuilder.add("alder32", String.valueOf(alder32Hash));
+      JsonObjectBuilder jsonObjectBuilder = JsonUtil.objectBuilderWithValueAtPath("properties/" + propsId, propertiesReadStatusBuilder.build());
       status = JsonUtil.mergeAndUpdate(status, jsonObjectBuilder.build());
    }
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
index 27b6ba1cc9..a0e499ca13 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
@@ -483,7 +483,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
 
       this.serviceRegistry = serviceRegistry == null ? new ServiceRegistryImpl() : serviceRegistry;
 
-      this.serverStatus = new ServerStatus(this);
+      this.serverStatus = ServerStatus.getInstanceFor(this);
    }
 
    @Override
@@ -628,7 +628,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
       }
 
       configuration.parseProperties(propertiesFileUrl);
-      updateStatus("configuration", configuration.getStatus());
+      updateStatus(ServerStatus.CONFIGURATION_COMPONENT, configuration.getStatus());
 
       initializeExecutorServices();
 
@@ -4402,7 +4402,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
       configurationReloadDeployed.set(false);
       if (isActive()) {
          configuration.parseProperties(propertiesFileUrl);
-         updateStatus("configuration", configuration.getStatus());
+         updateStatus(ServerStatus.CONFIGURATION_COMPONENT, configuration.getStatus());
          deployReloadableConfigFromConfiguration();
       }
    }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerStatus.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerStatus.java
index 50fbe77533..8d9c927002 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerStatus.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerStatus.java
@@ -28,41 +28,57 @@ import org.apache.activemq.artemis.utils.JsonLoader;
 
 public class ServerStatus {
 
-   private final ActiveMQServerImpl server;
-   private final HashMap<String, String> immutableStateValues = new HashMap<>();
-   private JsonObject globalStatus = JsonLoader.createObjectBuilder().build();
+   public static final String SERVER_COMPONENT = "server";
+   public static final String CONFIGURATION_COMPONENT = "configuration";
+   public static final String JAAS_COMPONENT = SERVER_COMPONENT + "/jaas";
+
+   private static final ServerStatus instance = new ServerStatus();
 
-   public ServerStatus(ActiveMQServerImpl activeMQServer) {
-      this.server = activeMQServer;
-      immutableStateValues.put("version", server.getVersion().getFullVersion());
+   public static synchronized ServerStatus getInstanceFor(ActiveMQServerImpl activeMQServer) {
+      if (instance.server == null) {
+         instance.server = activeMQServer;
+         instance.immutableStateValues.put("version", instance.server.getVersion().getFullVersion());
+      }
+      return instance;
    }
 
+   public static synchronized ServerStatus getInstance() {
+      return instance;
+   }
+
+   private ActiveMQServerImpl server;
+   private final HashMap<String, String> immutableStateValues = new HashMap<>();
+   private JsonObject globalStatus = JsonLoader.createObjectBuilder().build();
+
    public synchronized String asJson() {
       updateServerStatus();
       return globalStatus.toString();
    }
 
    private synchronized void updateServerStatus() {
-      HashMap<String, String> snapshotOfServerStatusAttributes = new HashMap<>();
-      snapshotOfServerStatusAttributes.putAll(immutableStateValues);
-      snapshotOfServerStatusAttributes.put("identity", server.getIdentity());
-      SimpleString nodeId = server.getNodeID();
-      snapshotOfServerStatusAttributes.put("nodeId", nodeId == null ? null : nodeId.toString());
-      snapshotOfServerStatusAttributes.put("uptime", server.getUptime());
-      snapshotOfServerStatusAttributes.put("state", server.getState().toString());
-
-      update("server", JsonUtil.toJsonObject(snapshotOfServerStatusAttributes));
+      if (instance.server != null) {
+         HashMap<String, String> snapshotOfServerStatusAttributes = new HashMap<>();
+         snapshotOfServerStatusAttributes.putAll(immutableStateValues);
+         snapshotOfServerStatusAttributes.put("identity", server.getIdentity());
+         SimpleString nodeId = server.getNodeID();
+         snapshotOfServerStatusAttributes.put("nodeId", nodeId == null ? null : nodeId.toString());
+         snapshotOfServerStatusAttributes.put("uptime", server.getUptime());
+         snapshotOfServerStatusAttributes.put("state", server.getState().toString());
+
+         update(SERVER_COMPONENT, JsonUtil.toJsonObject(snapshotOfServerStatusAttributes));
+      }
    }
 
    public synchronized void update(String component, String statusJson) {
-      JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder();
-      jsonObjectBuilder.add(component, JsonUtil.readJsonObject(statusJson));
-      globalStatus = JsonUtil.mergeAndUpdate(globalStatus, jsonObjectBuilder.build());
+      update(component, JsonUtil.readJsonObject(statusJson));
+   }
+
+   public synchronized void update(String component, HashMap<String, String> statusAttributes) {
+      update(component, JsonUtil.toJsonObject(statusAttributes));
    }
 
-   public synchronized void update(String component, JsonObject componentStatus) {
-      JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder();
-      jsonObjectBuilder.add(component, componentStatus);
+   public synchronized void update(String componentPath, JsonObject componentStatus) {
+      JsonObjectBuilder jsonObjectBuilder = JsonUtil.objectBuilderWithValueAtPath(componentPath, componentStatus);
       globalStatus = JsonUtil.mergeAndUpdate(globalStatus, jsonObjectBuilder.build());
    }
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ReloadableProperties.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ReloadableProperties.java
index 396340402c..a9864429e8 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ReloadableProperties.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ReloadableProperties.java
@@ -19,6 +19,7 @@ package org.apache.activemq.artemis.spi.core.security.jaas;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -30,9 +31,14 @@ import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
 
 import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+import org.apache.activemq.artemis.core.server.impl.ServerStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import java.lang.invoke.MethodHandles;
+import java.util.zip.Adler32;
+import java.util.zip.Checksum;
+
+import static org.apache.activemq.artemis.core.server.impl.ServerStatus.JAAS_COMPONENT;
 
 public class ReloadableProperties {
 
@@ -47,6 +53,8 @@ public class ReloadableProperties {
    private Map<String, Pattern> regexpProps;
    private long reloadTime = -1;
    private final PropertiesLoader.FileNameKey key;
+   private final Checksum adler32 = new Adler32();
+   private long checksum = 0;
 
    public ReloadableProperties(PropertiesLoader.FileNameKey key) {
       this.key = key;
@@ -58,9 +66,25 @@ public class ReloadableProperties {
 
    public synchronized ReloadableProperties obtained() {
       if (reloadTime < 0 || (key.isReload() && hasModificationAfter(reloadTime))) {
-         props = new Properties();
+
+         adler32.reset();
+         props = new Properties() {
+            // want to checksum in read order
+            @Override
+            public synchronized Object put(Object key, Object value) {
+               String sKey = key instanceof String ? (String)key : null;
+               String sValue = value instanceof String ? (String)value : null;
+               if (sKey != null && sValue != null) {
+                  adler32.update(sKey.getBytes(StandardCharsets.UTF_8));
+                  adler32.update('=');
+                  adler32.update(sValue.getBytes(StandardCharsets.UTF_8));
+               }
+               return super.put(key, value);
+            }
+         };
          try {
             load(key.file(), props);
+            checksum = adler32.getValue();
             invertedProps = null;
             invertedValueProps = null;
             regexpProps = null;
@@ -74,10 +98,18 @@ public class ReloadableProperties {
             }
          }
          reloadTime = System.currentTimeMillis();
+         updateStatus();
       }
       return this;
    }
 
+   private void updateStatus() {
+      HashMap<String, String> statusAttributes = new HashMap<>();
+      statusAttributes.put("Alder32", String.valueOf(checksum));
+      statusAttributes.put("reloadTime", String.valueOf(reloadTime));
+      ServerStatus.getInstance().update(JAAS_COMPONENT + "/properties/" + key.file.getName(),  statusAttributes);
+   }
+
    public synchronized Map<String, String> invertedPropertiesMap() {
       if (invertedProps == null) {
          invertedProps = new HashMap<>(props.size());
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImplTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImplTest.java
index fb10c84f66..1c35aa9eee 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImplTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImplTest.java
@@ -1337,6 +1337,7 @@ public class ConfigurationImplTest extends ActiveMQTestBase {
 
       assertTrue(jsonStatus.contains(UPDATED_SHA));
       assertFalse(jsonStatus.contains(SHA));
+      assertTrue(jsonStatus.contains("alder32"));
    }
 
    /**
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleTest.java
index 540c722183..1f5274388d 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleTest.java
@@ -25,19 +25,25 @@ import javax.security.auth.callback.UnsupportedCallbackException;
 import javax.security.auth.login.FailedLoginException;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
 
+import org.apache.activemq.artemis.core.server.impl.ServerStatus;
 import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
 import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
 import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import java.lang.invoke.MethodHandles;
+import java.util.zip.Adler32;
+import java.util.zip.Checksum;
+
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -104,6 +110,9 @@ public class PropertiesLoginModuleTest extends Assert {
       assertEquals("Should have one user principal", 1, subject.getPrincipals(UserPrincipal.class).size());
       assertEquals("Should have two group principals", 2, subject.getPrincipals(RolePrincipal.class).size());
 
+      String hashOriginal = genHash(usersFile);
+      assertTrue("Contains hash", ServerStatus.getInstance().asJson().contains(hashOriginal));
+
       context.logout();
 
       assertEquals("Should have zero principals", 0, subject.getPrincipals().size());
@@ -127,9 +136,28 @@ public class PropertiesLoginModuleTest extends Assert {
 
       context.logout();
 
+      String hashUpdated = genHash(usersFile);
+      assertTrue("Contains hashUpdated", ServerStatus.getInstance().asJson().contains(hashUpdated));
+      assertNotEquals(hashOriginal, hashUpdated);
+
       assertEquals("Should have zero principals", 0, subject.getPrincipals().size());
    }
 
+   private String genHash(File usersFile) {
+      Checksum hash = new Adler32();
+
+      try (FileReader fileReader = new FileReader(usersFile); BufferedReader bufferedReader = new BufferedReader(fileReader)) {
+         String line = null;
+         while ((line = bufferedReader.readLine()) != null) {
+            if (!line.startsWith("#") && !line.isBlank()) {
+               hash.update(line.getBytes(StandardCharsets.UTF_8));
+            }
+         }
+      } catch (Exception expectedEof) {
+      }
+      return String.valueOf(hash.getValue());
+   }
+
    @Test
    public void testBadUseridLogin() throws Exception {
       LoginContext context = new LoginContext("PropertiesLogin", new UserPassHandler("BAD", "secret"));