Allow modification of user vm details if user.vm.readonly.details is empty
diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java b/api/src/main/java/org/apache/cloudstack/query/QueryService.java
index 6a20c2f..19c11bf 100644
--- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java
+++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java
@@ -17,6 +17,7 @@
 package org.apache.cloudstack.query;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.cloudstack.affinity.AffinityGroupResponse;
@@ -120,7 +121,7 @@
 
     ConfigKey<String> UserVMReadOnlyDetails = new ConfigKey<>(String.class,
     "user.vm.readonly.details", "Advanced", "dataDiskController, rootDiskController",
-            "List of read-only VM settings/details as comma separated string", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.CSV, null);
+            "List of read-only VM settings/details as comma separated string", true, Collections.singletonList(ConfigKey.Scope.Global), null, null, null, null, null, ConfigKey.Kind.CSV, null, "");
 
     ConfigKey<Boolean> SortKeyAscending = new ConfigKey<>("Advanced", Boolean.class, "sortkey.algorithm", "true",
             "Sort algorithm - ascending or descending - to use. For entities that use sort key(template, disk offering, service offering, " +
diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
index 0ea910f..a25a99e 100644
--- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
+++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
@@ -267,10 +267,18 @@
 
     static ConfigDepotImpl s_depot = null;
 
-    static public void init(ConfigDepotImpl depot) {
+    private String _defaultValueIfEmpty = null;
+
+    public static void init(ConfigDepotImpl depot) {
         s_depot = depot;
     }
 
+    public ConfigKey(Class<T> type, String name, String category, String defaultValue, String description, boolean isDynamic, List<Scope> scopes, T multiplier,
+                     String displayText, String parent, Ternary<String, String, Long> group, Pair<String, Long> subGroup, Kind kind, String options, String defaultValueIfEmpty) {
+        this(type, name, category, defaultValue, description, isDynamic, scopes, multiplier, displayText, parent, group, subGroup, kind, options);
+        this._defaultValueIfEmpty = defaultValueIfEmpty;
+    }
+
     public ConfigKey(String category, Class<T> type, String name, String defaultValue, String description, boolean isDynamic, Scope scope) {
         this(type, name, category, defaultValue, description, isDynamic, scope, null);
     }
@@ -380,7 +388,19 @@
     public T value() {
         if (_value == null || isDynamic()) {
             String value = s_depot != null ? s_depot.getConfigStringValue(_name, Scope.Global, null) : null;
-            _value = valueOf((value == null) ? defaultValue() : value);
+
+            String effective;
+            if (value != null) {
+                if (value.isEmpty() && _defaultValueIfEmpty != null) {
+                    effective = _defaultValueIfEmpty;
+                } else {
+                    effective = value;
+                }
+            } else {
+                effective = _defaultValueIfEmpty != null ? _defaultValueIfEmpty : defaultValue();
+            }
+
+            _value = valueOf(effective);
         }
         return _value;
     }
@@ -409,6 +429,9 @@
             return valueInGlobalOrAvailableParentScope(scope, id);
         }
         logger.trace("Scope({}) value for config ({}): {}", scope, _name, _value);
+        if (value.isEmpty() && _defaultValueIfEmpty != null) {
+            return valueOf(_defaultValueIfEmpty);
+        }
         return valueOf(value);
     }