From 9176aa9c2249cf1acb758d8844fa3ff1eea6b929 Mon Sep 17 00:00:00 2001 From: Torsten Krause Date: Tue, 21 Jul 2026 09:37:23 +0200 Subject: [PATCH 1/6] MCR-3780 create common base class for source implementations --- .../instantiator/MCRInstantiatorUtils.java | 134 +--------------- .../source/MCRInstanceListSource.java | 69 +++++++-- .../source/MCRInstanceMapSource.java | 64 ++++++-- .../source/MCRInstanceSource.java | 69 ++++++--- .../source/MCRPropertyListSource.java | 128 +++++++--------- .../source/MCRPropertyMapSource.java | 119 ++++++--------- .../source/MCRPropertySource.java | 99 +++++------- .../source/MCRRawPropertiesSource.java | 5 +- .../config/instantiator/source/MCRSource.java | 3 +- .../instantiator/source/MCRSourceBase.java | 144 ++++++++++++++++++ .../instantiator/source/MCRSourceContext.java | 128 ++++++++++++++++ 11 files changed, 584 insertions(+), 378 deletions(-) create mode 100644 mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSourceBase.java create mode 100644 mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSourceContext.java diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/MCRInstantiatorUtils.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/MCRInstantiatorUtils.java index 2ee57e1863..cd0b6ce5e0 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/instantiator/MCRInstantiatorUtils.java +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/MCRInstantiatorUtils.java @@ -19,20 +19,12 @@ package org.mycore.common.config.instantiator; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.List; import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.SortedMap; -import java.util.TreeMap; import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.mycore.common.MCRClassTools; import org.mycore.common.config.MCRConfigurationException; -import org.mycore.common.config.annotation.MCRSentinel; import org.mycore.common.config.instantiator.source.MCRSource; import org.mycore.common.config.instantiator.target.MCRTarget; @@ -42,8 +34,6 @@ @SuppressWarnings("PMD.AvoidDuplicateLiterals") public final class MCRInstantiatorUtils { - private static final Logger LOGGER = LogManager.getLogger(); - private MCRInstantiatorUtils() { } @@ -56,43 +46,6 @@ public static Class getClass(String property, String className) { } } - public static Object createInstance(MCRTarget target, MCRInstanceConfiguration configuration, - MCRSentinel sentinel, String description) { - - String property = configuration.name().canonical(); - - if (sentinel != null) { - boolean sentinelValue = sentinel.defaultValue(); - String configuredSentinelValue = configuration.properties().remove(sentinel.name()); - if (configuredSentinelValue != null) { - sentinelValue = Boolean.parseBoolean(configuredSentinelValue); - } - if (sentinelValue == sentinel.rejectionValue()) { - if (LOGGER.isInfoEnabled()) { - LOGGER.info("[SENTINEL] Ignoring {} {} and all sub-properties", description, property); - } - return null; - } - } - - if (!configuration.instantiatable()) { - if (LOGGER.isInfoEnabled()) { - LOGGER.info("[CLEAN-UP] Ignoring {} {} and all sub-properties (no or empty class name)", - description, property); - } - return null; - } - - Object instance = configuration.instantiate(); - - if (!configuration.valueClass().isAssignableFrom(instance.getClass())) { - throw incompatibilityException(property, target, configuration.valueClass(), instance); - } - - return instance; - - } - public static String methodNames(List methods) { return methods.stream() .map(Method::getName) @@ -113,14 +66,6 @@ public static String annotationClassName(MCRSource source) { return source.annotationClass().getName(); } - public static String property(MCRInstanceConfiguration configuration, String annotationName) { - if (Objects.equals("", annotationName)) { - return configuration.name().canonical(); - } else { - return configuration.name().canonical() + "." + annotationName; - } - } - public static MCRConfigurationException emptyNameException(MCRTarget target) { return new MCRConfigurationException("The name for target " + targetTypeName(target) + " '" + target.name() + "' in configured class " + target.declaringClass().getName() + " must not be empty"); @@ -128,17 +73,9 @@ public static MCRConfigurationException emptyNameException(MCRTarget target) { public static MCRConfigurationException incompatibilityException(String property, Class instanceClass, Class superClass) { - return new MCRConfigurationException("Instance of class " + instanceClass.getName() - + ", configured in " + property + ", is incompatible with" + - " intended super class " + superClass.getName()); - } - - public static MCRConfigurationException incompatibilityException(String property, MCRTarget target, - Class annotationValueClass, Object instance) { - return new MCRConfigurationException("Instance of class " + instance.getClass().getName() - + "', configured in " + property + ", is incompatible with annotated value class " - + annotationValueClass.getName() + " for target " + targetTypeName(target) - + " '" + target.name() + "' in configured class " + target.declaringClass().getName()); + return new MCRConfigurationException("Instance, configured in " + property + ", has a class (" + + instanceClass.getName() + ") that is incompatible with the intended super class (" + + superClass.getName() + ")"); } public static MCRConfigurationException missingException(String property, Class superClass) { @@ -147,73 +84,16 @@ public static MCRConfigurationException missingException(String property, Class< + " and therefore cannot be used implicitly)"); } - public static MCRConfigurationException missingException(String property, MCRTarget target, - String description) { - return new MCRConfigurationException( - capitalize(description) + ", configured in " + property + " (and its sub-properties)," + - " for target " + targetTypeName(target) + " '" + target.name() + "' in configured class " - + target.declaringClass().getName() - + " is missing"); - } - - public static MCRConfigurationException emptyException(String property, MCRTarget target, String description) { - return new MCRConfigurationException( - capitalize(description) + ", configured in " + property + " (and its sub-properties)," + - " for target " + targetTypeName(target) + " '" + target.name() + "' in configured class " - + target.declaringClass().getName() - + " is empty"); - } - public static MCRConfigurationException emptyRawException(String property, MCRTarget target, String description) { - return new MCRConfigurationException( - capitalize(description) + ", configured in " + property + "," + - " for target " + targetTypeName(target) + " '" + target.name() + "' in configured class " - + target.declaringClass().getName() - + " is empty"); - } - - public static MCRConfigurationException nonIntegerKeyException(String property, MCRTarget target, - String key, String description, NumberFormatException exception) { - return new MCRConfigurationException( - capitalize(description) + ", configured in " + property + " (and its sub-properties)," + - " for target " + targetTypeName(target) + " '" + target.name() + "' in configured class " - + target.declaringClass().getName() + " has element with non-integer key " + key, - exception); + return new MCRConfigurationException(capitalize(description) + ", configured in " + property + "," + + " for target " + targetTypeName(target) + " '" + target.name() + "' in configured class " + + target.declaringClass().getName() + + " is empty"); } public static String capitalize(String description) { return description.substring(0, 1).toUpperCase(Locale.ROOT) + description.substring(1); } - public static MCRConfigurationException inconsistentKeysException(String property, MCRTarget target, - String key1, String key2, String description) { - return new MCRConfigurationException( - capitalize(description) + ", configured in " + property + " (and its sub-properties)," - + " for target " + targetTypeName(target) + " '" + target.name() + "' in configured class " - + target.declaringClass().getName() + " has element with inconsistent integer keys " - + key1 + " and " + key2); - } - - @SuppressWarnings("PMD.PreserveStackTrace") - public static List orderedKeys(String property, MCRTarget target, Map map, - String description) { - - SortedMap keyMap = new TreeMap<>(); - for (String key : map.keySet()) { - try { - Integer integerValue = Integer.parseInt(key); - String alreadyMappedKey = keyMap.put(integerValue, key); - if (alreadyMappedKey != null && !alreadyMappedKey.equals(key)) { - throw inconsistentKeysException(property, target, key, alreadyMappedKey, description); - } - } catch (NumberFormatException e) { - throw nonIntegerKeyException(property, target, key, description, e); - } - } - - return new ArrayList<>(keyMap.values()); - - } - } diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRInstanceListSource.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRInstanceListSource.java index 5dd4e6b7df..3f169034d6 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRInstanceListSource.java +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRInstanceListSource.java @@ -18,16 +18,12 @@ package org.mycore.common.config.instantiator.source; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.createInstance; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.emptyException; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.orderedKeys; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.property; - import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import org.mycore.common.config.MCRConfigurationException; import org.mycore.common.config.annotation.MCRInstanceList; import org.mycore.common.config.annotation.MCRSentinel; import org.mycore.common.config.instantiator.MCRInstanceConfiguration; @@ -36,7 +32,7 @@ /** * A {@link MCRInstanceListSource} is a {@link MCRSource} that interprets a {@link MCRInstanceList}. */ -final class MCRInstanceListSource implements MCRSource { +final class MCRInstanceListSource extends MCRSourceBase> { private final MCRInstanceList annotation; @@ -73,29 +69,74 @@ public Class valueClass() { } @Override - public List get(MCRInstanceConfiguration configuration, MCRTarget target) { + protected String description() { + return "instance list"; + } + + @Override + protected String name() { + return annotation.name(); + } + + @Override + protected boolean allowsEmptyName() { + return true; + } + + @Override + protected boolean absolute() { + return false; + } + + @Override + protected boolean required() { + return annotation.required(); + } + + @Override + protected String defaultName() { + return ""; + } + + @Override + protected List getResult(MCRSourceContext context, MCRInstanceConfiguration configuration, + Map properties, String prefix) { Map> nestedConfigurationMap = configuration.nestedMap(annotation.valueClass(), annotation.name()); - List keyList = orderedKeys(property(configuration, annotation.name()), target, - nestedConfigurationMap, "instance list"); - List instanceList = new ArrayList<>(nestedConfigurationMap.size()); + + List keyList = context.orderedKeys(nestedConfigurationMap); for (String key : keyList) { + + MCRSourceContext nestedContext = context.nested(key, "instance list element"); MCRInstanceConfiguration nestedConfiguration = nestedConfigurationMap.get(key); - Object instance = createInstance(target, nestedConfiguration, sentinel, "instance list element"); + Object instance = createInstance(nestedContext, nestedConfiguration, sentinel); + if (instance != null) { instanceList.add(instance); } - } - if (instanceList.isEmpty() && annotation.required()) { - throw emptyException(property(configuration, annotation.name()), target, "instance list"); } return instanceList; } + @Override + protected boolean isMissingResult(List result) { + return result.isEmpty(); + } + + @Override + protected MCRConfigurationException missingException(MCRSourceContext context) { + return context.emptyException(); + } + + @Override + protected List nullResultReplacement() { + return new ArrayList<>(); + } + } diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRInstanceMapSource.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRInstanceMapSource.java index 05b369675c..2edb1fe174 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRInstanceMapSource.java +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRInstanceMapSource.java @@ -18,14 +18,11 @@ package org.mycore.common.config.instantiator.source; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.createInstance; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.emptyException; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.property; - import java.util.HashMap; import java.util.Map; import java.util.Set; +import org.mycore.common.config.MCRConfigurationException; import org.mycore.common.config.annotation.MCRInstanceMap; import org.mycore.common.config.annotation.MCRSentinel; import org.mycore.common.config.instantiator.MCRInstanceConfiguration; @@ -34,7 +31,7 @@ /** * A {@link MCRInstanceMapSource} is a {@link MCRSource} that interprets a {@link MCRInstanceMap}. */ -final class MCRInstanceMapSource implements MCRSource { +final class MCRInstanceMapSource extends MCRSourceBase> { private final MCRInstanceMap annotation; @@ -71,26 +68,73 @@ public Class valueClass() { } @Override - public Map get(MCRInstanceConfiguration configuration, MCRTarget target) { + protected String description() { + return "instance map"; + } + + @Override + protected String name() { + return annotation.name(); + } + + @Override + protected boolean allowsEmptyName() { + return true; + } + + @Override + protected boolean absolute() { + return false; + } + + @Override + protected boolean required() { + return annotation.required(); + } + + @Override + protected String defaultName() { + return ""; + } + + @Override + protected Map getResult(MCRSourceContext context, MCRInstanceConfiguration configuration, + Map properties, String prefix) { Map> nestedConfigurationMap = configuration.nestedMap(annotation.valueClass(), annotation.name()); Map instanceMap = new HashMap<>(); + for (String key : nestedConfigurationMap.keySet()) { + + MCRSourceContext nestedContext = context.nested(key, "instance map entry"); MCRInstanceConfiguration nestedConfiguration = nestedConfigurationMap.get(key); - Object instance = createInstance(target, nestedConfiguration, sentinel, "instance map entry"); + Object instance = createInstance(nestedContext, nestedConfiguration, sentinel); + if (instance != null) { instanceMap.put(key, instance); } - } - if (instanceMap.isEmpty() && annotation.required()) { - throw emptyException(property(configuration, annotation.name()), target, "instance map"); } return instanceMap; } + @Override + protected boolean isMissingResult(Map result) { + return result.isEmpty(); + } + + @Override + protected MCRConfigurationException missingException(MCRSourceContext context) { + return context.emptyException(); + } + + @Override + protected Map nullResultReplacement() { + return new HashMap<>(); + } + } diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRInstanceSource.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRInstanceSource.java index 1960a96fb8..c5bc68576e 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRInstanceSource.java +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRInstanceSource.java @@ -18,13 +18,10 @@ package org.mycore.common.config.instantiator.source; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.createInstance; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.emptyNameException; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.missingException; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.property; - +import java.util.Map; import java.util.Set; +import org.mycore.common.config.MCRConfigurationException; import org.mycore.common.config.annotation.MCRInstance; import org.mycore.common.config.annotation.MCRSentinel; import org.mycore.common.config.instantiator.MCRInstanceConfiguration; @@ -33,10 +30,7 @@ /** * A {@link MCRInstanceSource} is a {@link MCRSource} that interprets a {@link MCRInstance}. */ -@SuppressWarnings({ "PMD.MCR.Singleton.ClassModifiers", "PMD.MCR.Singleton.PrivateConstructor", - "PMD.MCR.Singleton.NonPrivateConstructors", "PMD.MCR.Singleton.MethodModifiers", - "PMD.MCR.Singleton.MethodReturnType", "PMD.SingletonClassReturningNewInstance" }) -final class MCRInstanceSource implements MCRSource { +final class MCRInstanceSource extends MCRSourceBase { private final MCRInstance annotation; @@ -73,22 +67,57 @@ public Class valueClass() { } @Override - public Object get(MCRInstanceConfiguration configuration, MCRTarget target) { + protected String description() { + return "instance"; + } - String name = annotation.name(); - if (name.isEmpty()) { - throw emptyNameException(target); - } + @Override + protected String name() { + return annotation.name(); + } - MCRInstanceConfiguration nestedConfiguration = configuration.nested(annotation.valueClass(), name); - Object instance = createInstance(target, nestedConfiguration, sentinel, "instance"); + @Override + protected boolean allowsEmptyName() { + return false; + } - if (instance == null && annotation.required()) { - throw missingException(property(configuration, annotation.name()), target, "instance"); - } + @Override + protected boolean absolute() { + return false; + } + + @Override + protected boolean required() { + return annotation.required(); + } - return instance; + @Override + protected String defaultName() { + return ""; + } + @Override + protected Object getResult(MCRSourceContext context, MCRInstanceConfiguration configuration, + Map properties, String prefix) { + + MCRInstanceConfiguration nestedConfiguration = configuration.nested(annotation.valueClass(), name()); + return createInstance(context, nestedConfiguration, sentinel); + + } + + @Override + protected boolean isMissingResult(Object result) { + return result == null; + } + + @Override + protected MCRConfigurationException missingException(MCRSourceContext context) { + return context.missingException(); + } + + @Override + protected Object nullResultReplacement() { + return null; } } diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRPropertyListSource.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRPropertyListSource.java index 99e7baf9ee..6c50d4164d 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRPropertyListSource.java +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRPropertyListSource.java @@ -18,9 +18,6 @@ package org.mycore.common.config.instantiator.source; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.emptyException; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.orderedKeys; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -28,9 +25,8 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.mycore.common.config.MCRConfiguration2; +import org.mycore.common.config.MCRConfigurationException; import org.mycore.common.config.annotation.MCRPropertyList; import org.mycore.common.config.annotation.MCRSentinel; import org.mycore.common.config.instantiator.MCRInstanceConfiguration; @@ -39,9 +35,7 @@ /** * A {@link MCRPropertyListSource} is a {@link MCRSource} that interprets a {@link MCRPropertyList}. */ -final class MCRPropertyListSource implements MCRSource { - - private static final Logger LOGGER = LogManager.getLogger(); +final class MCRPropertyListSource extends MCRSourceBase> { private final MCRPropertyList annotation; @@ -78,60 +72,43 @@ public Class valueClass() { } @Override - public List get(MCRInstanceConfiguration configuration, MCRTarget target) { - - Map fullProperties = configuration.fullProperties(); - - String property; - String description; - List propertyList; - if (annotation.absolute()) { - property = annotation.name(); - description = "absolute property list"; - Map properties = fullProperties; - propertyList = getPropertyList(property, annotation.name(), ".", target, properties, description); - } else { - if (annotation.name().isEmpty()) { - property = configuration.name().canonical(); - description = "property list"; - Map properties = configuration.properties(); - propertyList = getPropertyList(property, "", "", target, properties, description); - } else { - property = configuration.name().canonical() + "." + annotation.name(); - description = "property list"; - Map properties = configuration.properties(); - propertyList = getPropertyList(property, annotation.name(), ".", target, properties, description); - } - } - - String defaultName = annotation.defaultName(); - if (propertyList == null && !defaultName.isEmpty()) { - - property = defaultName; - description = "default property list"; - propertyList = getPropertyList(defaultName, defaultName, ".", target, fullProperties, description); + protected String description() { + return "property list"; + } - if (propertyList == null || (propertyList.isEmpty() && annotation.required())) { - throw emptyException(property, target, description); - } + @Override + protected String name() { + return annotation.name(); + } - } + @Override + protected boolean allowsEmptyName() { + return true; + } - if ((propertyList == null || propertyList.isEmpty()) && annotation.required()) { - throw emptyException(property, target, description); - } + @Override + protected boolean absolute() { + return annotation.absolute(); + } - return propertyList == null ? new ArrayList<>() : propertyList; + @Override + protected boolean required() { + return annotation.required(); + } + @Override + protected String defaultName() { + return annotation.defaultName(); } - private List getPropertyList(String property, String prefix, String delimiter, - MCRTarget target, Map properties, String description) { + @Override + protected List getResult(MCRSourceContext context, MCRInstanceConfiguration configuration, + Map properties, String prefix) { AtomicBoolean hasRelevantProperty = new AtomicBoolean(false); - Map rawPropertyMap = new HashMap<>(); - String keyPrefix = prefix + delimiter; + Map listProperties = new HashMap<>(); + String keyPrefix = prefix.isEmpty() ? prefix : prefix + "."; int keyPrefixLength = keyPrefix.length(); properties.forEach((key, value) -> { if (key.startsWith(keyPrefix) && !key.isEmpty()) { @@ -139,39 +116,25 @@ private List getPropertyList(String property, String prefix, String deli if (index == -1) { if (!value.isEmpty()) { hasRelevantProperty.set(true); - rawPropertyMap.put(key.substring(keyPrefixLength), value); + listProperties.put(key.substring(keyPrefixLength), value); } } } }); List headPropertyList = new ArrayList<>(0); - List tailPropertyList = new ArrayList<>(rawPropertyMap.size()); + List tailPropertyList = new ArrayList<>(listProperties.size()); - List keyList = orderedKeys(property, target, rawPropertyMap, description); + List keyList = context.orderedKeys(listProperties); for (String key : keyList) { - String value = rawPropertyMap.get(key); - if (sentinel != null) { - boolean sentinelValue = sentinel.defaultValue(); - String configuredSentinelValue = properties.get(keyPrefix + key + "." + sentinel.name()); - if (configuredSentinelValue != null) { - sentinelValue = Boolean.parseBoolean(configuredSentinelValue); - } - if (sentinelValue == sentinel.rejectionValue()) { - if (LOGGER.isInfoEnabled()) { - LOGGER.info("[SENTINEL] Ignoring {} element {}.{} and all sub-properties", - description, property, key); - } - continue; + MCRSourceContext nestedContext = context.nested(key, "property list element"); + if (!rejectedBySentinel(sentinel, nestedContext, properties, keyPrefix + key + ".")) { + if (key.charAt(0) == '-') { + headPropertyList.add(listProperties.get(key)); + } else { + tailPropertyList.add(listProperties.get(key)); } } - - if (key.charAt(0) == '-') { - headPropertyList.add(value); - } else { - tailPropertyList.add(value); - } - } List shortFormList = List.of(); @@ -195,4 +158,19 @@ private List parseShortFormList(String value) { return MCRConfiguration2.splitValue(value).toList(); } + @Override + protected boolean isMissingResult(List result) { + return result.isEmpty(); + } + + @Override + protected MCRConfigurationException missingException(MCRSourceContext context) { + return context.emptyException(); + } + + @Override + protected List nullResultReplacement() { + return new ArrayList<>(); + } + } diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRPropertyMapSource.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRPropertyMapSource.java index ae1da1cb00..b06ecea6c9 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRPropertyMapSource.java +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRPropertyMapSource.java @@ -18,17 +18,14 @@ package org.mycore.common.config.instantiator.source; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.emptyException; - import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.mycore.common.config.MCRConfiguration2; +import org.mycore.common.config.MCRConfigurationException; import org.mycore.common.config.annotation.MCRPropertyMap; import org.mycore.common.config.annotation.MCRSentinel; import org.mycore.common.config.instantiator.MCRInstanceConfiguration; @@ -37,9 +34,7 @@ /** * A {@link MCRPropertyMapSource} is a {@link MCRSource} that interprets a {@link MCRPropertyMap}. */ -final class MCRPropertyMapSource implements MCRSource { - - private static final Logger LOGGER = LogManager.getLogger(); +final class MCRPropertyMapSource extends MCRSourceBase> { private final MCRPropertyMap annotation; @@ -76,55 +71,38 @@ public Class valueClass() { } @Override - public Map get(MCRInstanceConfiguration configuration, MCRTarget target) { - - Map fullProperties = configuration.fullProperties(); - - String property; - String description; - Map propertyMap; - if (annotation.absolute()) { - property = annotation.name(); - description = "absolute property map"; - Map properties = fullProperties; - propertyMap = getPropertyMap(property, annotation.name(), ".", properties, description); - } else { - if (annotation.name().isEmpty()) { - property = configuration.name().canonical(); - description = "property map"; - Map properties = configuration.properties(); - propertyMap = getPropertyMap(property, "", "", properties, description); - } else { - property = configuration.name().canonical() + "." + annotation.name(); - description = "property map"; - Map properties = configuration.properties(); - propertyMap = getPropertyMap(property, annotation.name(), ".", properties, description); - } - } - - String defaultName = annotation.defaultName(); - if (propertyMap == null && !defaultName.isEmpty()) { - - property = defaultName; - description = "default property map"; - propertyMap = getPropertyMap(defaultName, defaultName, ".", fullProperties, description); + protected String description() { + return "property map"; + } - if (propertyMap == null || (propertyMap.isEmpty() && annotation.required())) { - throw emptyException(property, target, description); - } + @Override + protected String name() { + return annotation.name(); + } - } + @Override + protected boolean allowsEmptyName() { + return true; + } - if ((propertyMap == null || propertyMap.isEmpty()) && annotation.required()) { - throw emptyException(property, target, description); - } + @Override + protected boolean absolute() { + return annotation.absolute(); + } - return propertyMap == null ? new HashMap<>() : propertyMap; + @Override + protected boolean required() { + return annotation.required(); + } + @Override + protected String defaultName() { + return annotation.defaultName(); } - private Map getPropertyMap(String property, String prefix, String delimiter, - Map properties, String description) { + @Override + protected Map getResult(MCRSourceContext context, MCRInstanceConfiguration configuration, + Map properties, String prefix) { AtomicBoolean hasRelevantProperty = new AtomicBoolean(false); @@ -135,8 +113,8 @@ private Map getPropertyMap(String property, String prefix, Strin shortFormMap = parseShortFormMap(shortFormProperty); } - Map rawPropertyMap = new HashMap<>(shortFormMap); - String keyPrefix = prefix + delimiter; + Map mapProperties = new HashMap<>(shortFormMap); + String keyPrefix = prefix.isEmpty() ? prefix : prefix + "."; int keyPrefixLength = keyPrefix.length(); properties.forEach((key, value) -> { if (key.startsWith(keyPrefix) && !key.isEmpty()) { @@ -144,7 +122,7 @@ private Map getPropertyMap(String property, String prefix, Strin if (index == -1) { if (!value.isEmpty()) { hasRelevantProperty.set(true); - rawPropertyMap.put(key.substring(keyPrefixLength), value); + mapProperties.put(key.substring(keyPrefixLength), value); } } } @@ -152,25 +130,11 @@ private Map getPropertyMap(String property, String prefix, Strin Map propertyMap = new HashMap<>(); - for (String key : rawPropertyMap.keySet()) { - String value = rawPropertyMap.get(key); - if (sentinel != null) { - boolean sentinelValue = sentinel.defaultValue(); - String configuredSentinelValue = properties.get(keyPrefix + key + "." + sentinel.name()); - if (configuredSentinelValue != null) { - sentinelValue = Boolean.parseBoolean(configuredSentinelValue); - } - if (sentinelValue == sentinel.rejectionValue()) { - if (LOGGER.isInfoEnabled()) { - LOGGER.info("[SENTINEL] Ignoring {} entry {}.{} and all sub-properties", - description, property, key); - } - continue; - } + for (String key : mapProperties.keySet()) { + MCRSourceContext nestedContext = context.nested(key, "property map entry"); + if (!rejectedBySentinel(sentinel, nestedContext, properties, keyPrefix + key + ".")) { + propertyMap.put(key, mapProperties.get(key)); } - - propertyMap.put(key, value); - } return hasRelevantProperty.get() ? propertyMap : null; @@ -185,4 +149,19 @@ private Map parseShortFormMap(String value) { .collect(Collectors.toMap(parts -> parts[0].trim(), parts -> parts[1].trim())); } + @Override + protected boolean isMissingResult(Map result) { + return result.isEmpty(); + } + + @Override + protected MCRConfigurationException missingException(MCRSourceContext context) { + return context.emptyException(); + } + + @Override + protected Map nullResultReplacement() { + return new HashMap<>(); + } + } diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRPropertySource.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRPropertySource.java index a76d817746..4f13711a3b 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRPropertySource.java +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRPropertySource.java @@ -18,14 +18,10 @@ package org.mycore.common.config.instantiator.source; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.emptyNameException; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.missingException; - import java.util.Map; import java.util.Set; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.mycore.common.config.MCRConfigurationException; import org.mycore.common.config.annotation.MCRProperty; import org.mycore.common.config.annotation.MCRSentinel; import org.mycore.common.config.instantiator.MCRInstanceConfiguration; @@ -34,9 +30,7 @@ /** * A {@link MCRPropertySource} is a {@link MCRSource} that interprets a {@link MCRProperty}. */ -final class MCRPropertySource implements MCRSource { - - private static final Logger LOGGER = LogManager.getLogger(); +final class MCRPropertySource extends MCRSourceBase { private final MCRProperty annotation; @@ -73,69 +67,60 @@ public Class valueClass() { } @Override - public String get(MCRInstanceConfiguration configuration, MCRTarget target) { + protected String description() { + return "property"; + } - String name = annotation.name(); - if (name.isEmpty()) { - throw emptyNameException(target); - } + @Override + protected String name() { + return annotation.name(); + } - Map fullProperties = configuration.fullProperties(); - - String property; - String description; - String propertyValue; - if (annotation.absolute()) { - property = annotation.name(); - description = "absolute property"; - propertyValue = getPropertyValue(property, annotation.name(), fullProperties, description); - } else { - property = configuration.name().canonical() + "." + annotation.name(); - description = "property"; - propertyValue = getPropertyValue(property, annotation.name(), configuration.properties(), description); - } + @Override + protected boolean allowsEmptyName() { + return false; + } - String defaultName = annotation.defaultName(); - if (propertyValue == null && !defaultName.isEmpty()) { + @Override + protected boolean absolute() { + return annotation.absolute(); + } - property = defaultName; - description = "default property"; - propertyValue = getPropertyValue(defaultName, defaultName, fullProperties, description); + @Override + protected boolean required() { + return annotation.required(); + } - if (propertyValue == null) { - throw missingException(property, target, description); - } + @Override + protected String defaultName() { + return annotation.defaultName(); + } - } + @Override + protected String getResult(MCRSourceContext context, MCRInstanceConfiguration configuration, + Map properties, String prefix) { - if (propertyValue == null && annotation.required()) { - throw missingException(property, target, description); + if (rejectedBySentinel(sentinel, context, properties, prefix + ".")) { + return null; } - return propertyValue; + return properties.get(prefix); } - private String getPropertyValue(String property, String prefix, - Map properties, String description) { - - if (sentinel != null) { - boolean sentinelValue = sentinel.defaultValue(); - String configuredSentinelValue = properties.get(prefix + "." + sentinel.name()); - if (configuredSentinelValue != null) { - sentinelValue = Boolean.parseBoolean(configuredSentinelValue); - } - if (sentinelValue == sentinel.rejectionValue()) { - if (LOGGER.isInfoEnabled()) { - LOGGER.info("[SENTINEL] Ignoring {} {} and all sub-properties", description, property); - } - return null; - } - - } + @Override + protected boolean isMissingResult(String result) { + return result == null; + } - return properties.get(prefix); + @Override + protected MCRConfigurationException missingException(MCRSourceContext context) { + return context.missingException(); + } + @Override + protected String nullResultReplacement() { + return null; } } diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRRawPropertiesSource.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRRawPropertiesSource.java index a082764865..bd904d5294 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRRawPropertiesSource.java +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRRawPropertiesSource.java @@ -18,8 +18,6 @@ package org.mycore.common.config.instantiator.source; -import static org.mycore.common.config.instantiator.MCRInstantiatorUtils.emptyRawException; - import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -27,6 +25,7 @@ import org.mycore.common.config.MCRConfigurationException; import org.mycore.common.config.annotation.MCRRawProperties; import org.mycore.common.config.instantiator.MCRInstanceConfiguration; +import org.mycore.common.config.instantiator.MCRInstantiatorUtils; import org.mycore.common.config.instantiator.target.MCRTarget; /** @@ -98,7 +97,7 @@ public Map get(MCRInstanceConfiguration configuration, MCRTar property = configuration.name().canonical() + "." + annotation.namePattern(); description = "raw property map"; } - throw emptyRawException(property, target, description); + throw MCRInstantiatorUtils.emptyRawException(property, target, description); } return filteredProperties; diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSource.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSource.java index 74e1b4d261..34df2e9326 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSource.java +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSource.java @@ -43,8 +43,7 @@ * Common abstraction for components of a class (i.e., a {@link Field} or a {@link Method}) that can, * in principle, be used for injection. It provides an abstraction for obtaining a value based on present annotations. */ -public sealed interface MCRSource permits MCRInstanceListSource, MCRInstanceMapSource, MCRInstanceSource, - MCRPostConstructionSource, MCRPropertyListSource, MCRPropertyMapSource, MCRPropertySource, MCRRawPropertiesSource { +public interface MCRSource { Type type(); diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSourceBase.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSourceBase.java new file mode 100644 index 0000000000..dcdf891552 --- /dev/null +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSourceBase.java @@ -0,0 +1,144 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.common.config.instantiator.source; + +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.mycore.common.config.MCRConfigurationException; +import org.mycore.common.config.annotation.MCRSentinel; +import org.mycore.common.config.instantiator.MCRInstanceConfiguration; +import org.mycore.common.config.instantiator.MCRInstantiatorUtils; +import org.mycore.common.config.instantiator.target.MCRTarget; + +abstract class MCRSourceBase implements MCRSource { + + protected final Logger logger = LogManager.getLogger(getClass()); + + @Override + @SuppressWarnings("PMD.NPathComplexity") + public final Result get(MCRInstanceConfiguration configuration, MCRTarget target) { + + String name = name(); + MCRSourceContext context; + Result result; + + if (name.isEmpty() && (absolute() || !allowsEmptyName())) { + throw MCRInstantiatorUtils.emptyNameException(target); + } + + if (absolute()) { + context = new MCRSourceContext(target, name, "absolute " + description()); + result = getResult(context, configuration, configuration.fullProperties(), name); + } else if (name.isEmpty()) { + context = new MCRSourceContext(target, configuration.name().canonical(), description()); + result = getResult(context, configuration, configuration.properties(), ""); + } else { + context = new MCRSourceContext(target, configuration.name().canonical() + "." + name, description()); + result = getResult(context, configuration, configuration.properties(), name); + } + + String defaultName = defaultName(); + if (result == null && !defaultName.isEmpty()) { + context = new MCRSourceContext(target, defaultName, "default " + description()); + result = getResult(context, configuration, configuration.fullProperties(), defaultName); + if (result == null || (isMissingResult(result) && required())) { + throw missingException(context); + } + } + + if ((result == null || isMissingResult(result)) && required()) { + throw missingException(context); + } + + return result == null ? nullResultReplacement() : result; + + } + + protected abstract String description(); + + protected abstract String name(); + + protected abstract boolean allowsEmptyName(); + + protected abstract boolean absolute(); + + protected abstract boolean required(); + + protected abstract String defaultName(); + + protected abstract Result getResult(MCRSourceContext context, MCRInstanceConfiguration configuration, + Map properties, String prefix); + + protected abstract boolean isMissingResult(Result result); + + protected abstract MCRConfigurationException missingException(MCRSourceContext context); + + protected abstract Result nullResultReplacement(); + + protected final Object createInstance(MCRSourceContext context, MCRInstanceConfiguration configuration, + MCRSentinel sentinel) { + + if (rejectedBySentinel(sentinel, context, configuration.properties(), "")) { + return null; + } + + if (!configuration.instantiatable()) { + if (logger.isInfoEnabled()) { + logger.info("[CLEAN-UP] Ignoring {} {} and all sub-properties (no or empty class name)", + context.description(), context.property()); + } + return null; + } + + Object instance = configuration.instantiate(); + + if (!configuration.valueClass().isAssignableFrom(instance.getClass())) { + throw context.incompatibilityException(configuration.valueClass(), instance); + } + + return instance; + + } + + protected final boolean rejectedBySentinel(MCRSentinel sentinel, MCRSourceContext context, + Map properties, String prefix) { + + if (sentinel != null) { + boolean sentinelValue = sentinel.defaultValue(); + String configuredSentinelValue = properties.remove(prefix + sentinel.name()); + if (configuredSentinelValue != null) { + sentinelValue = Boolean.parseBoolean(configuredSentinelValue); + } + if (sentinelValue == sentinel.rejectionValue()) { + if (logger.isInfoEnabled()) { + logger.info("[SENTINEL] Ignoring {} {} and all sub-properties", + context.description(), context.property()); + } + return true; + } + + } + + return false; + + } + +} diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSourceContext.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSourceContext.java new file mode 100644 index 0000000000..53e98f06a6 --- /dev/null +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSourceContext.java @@ -0,0 +1,128 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.common.config.instantiator.source; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.mycore.common.config.MCRConfigurationException; +import org.mycore.common.config.instantiator.MCRInstantiatorUtils; +import org.mycore.common.config.instantiator.target.MCRTarget; + +public final class MCRSourceContext { + + private final MCRTarget target; + + private final String property; + + private final String description; + + private final List hints; + + public MCRSourceContext(MCRTarget target, String property, String description, String... hints) { + this(target, property, description, List.of(hints)); + } + + private MCRSourceContext(MCRTarget target, String property, String description, List hints) { + this.target = target; + this.property = property; + this.description = description; + this.hints = hints; + } + + public MCRTarget target() { + return target; + } + + public String property() { + return property; + } + + public String description() { + return description + (hints.isEmpty() ? "" : " (" + String.join(", ", hints) + ")"); + } + + public List hints() { + return hints; + } + + public MCRSourceContext nested(String suffix, String description) { + return new MCRSourceContext(target, property + "." + suffix, description, hints); + } + + public List orderedKeys(Map map) { + + SortedMap keyMap = new TreeMap<>(); + for (String key : map.keySet()) { + try { + Integer integerValue = Integer.parseInt(key); + String alreadyMappedKey = keyMap.put(integerValue, key); + if (alreadyMappedKey != null && !alreadyMappedKey.equals(key)) { + throw inconsistentKeysException(key, alreadyMappedKey); + } + } catch (NumberFormatException exception) { + throw nonIntegerKeyException(key, exception); + } + } + + return new ArrayList<>(keyMap.values()); + + } + + public MCRConfigurationException configurationException(String exceptionMessage) { + return new MCRConfigurationException(exceptionMessage(exceptionMessage)); + } + + public MCRConfigurationException configurationException(String exceptionMessage, Exception exception) { + return new MCRConfigurationException(exceptionMessage(exceptionMessage), exception); + } + + public MCRConfigurationException incompatibilityException( + Class annotationValueClass, Object instance) { + return configurationException("has a class (" + instance.getClass().getName() + ")," + + " that is incompatible with the annotated value class (" + annotationValueClass.getName() + ")"); + } + + public MCRConfigurationException missingException() { + return configurationException("is missing"); + } + + public MCRConfigurationException emptyException() { + return configurationException("is empty"); + } + + public MCRConfigurationException nonIntegerKeyException(String key, NumberFormatException exception) { + return configurationException("has element with non-integer key " + key, exception); + } + + public MCRConfigurationException inconsistentKeysException(String key1, String key2) { + return configurationException("has element with inconsistent integer keys " + key1 + " and " + key2); + } + + private String exceptionMessage(String exceptionMessage) { + return MCRInstantiatorUtils.capitalize(description()) + ", configured in " + property() + + " (and its sub-properties)," + " for target " + target.type().name().toLowerCase(Locale.ROOT) + " '" + + target.name() + "' in configured class " + target.declaringClass().getName() + " " + exceptionMessage; + } + +} From db17da01ce51c05ed73c7b7e5a49de84c6882fcb Mon Sep 17 00:00:00 2001 From: Torsten Krause Date: Wed, 22 Jul 2026 14:57:48 +0200 Subject: [PATCH 2/6] MCR-3784 allow absent default properties --- .../instantiator/source/MCRSourceBase.java | 3 --- .../config/MCRInstantiatorPropertyListTest.java | 17 ++++++----------- .../config/MCRInstantiatorPropertyMapTest.java | 17 ++++++----------- .../config/MCRInstantiatorPropertyTest.java | 8 +++++--- 4 files changed, 17 insertions(+), 28 deletions(-) diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSourceBase.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSourceBase.java index dcdf891552..2b1b9f46c9 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSourceBase.java +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/source/MCRSourceBase.java @@ -59,9 +59,6 @@ public final Result get(MCRInstanceConfiguration configuration, MCRTarget tar if (result == null && !defaultName.isEmpty()) { context = new MCRSourceContext(target, defaultName, "default " + description()); result = getResult(context, configuration, configuration.fullProperties(), defaultName); - if (result == null || (isMissingResult(result) && required())) { - throw missingException(context); - } } if ((result == null || isMissingResult(result)) && required()) { diff --git a/mycore-base/src/test/java/org/mycore/common/config/MCRInstantiatorPropertyListTest.java b/mycore-base/src/test/java/org/mycore/common/config/MCRInstantiatorPropertyListTest.java index 259e2cbfe4..06637a7d98 100644 --- a/mycore-base/src/test/java/org/mycore/common/config/MCRInstantiatorPropertyListTest.java +++ b/mycore-base/src/test/java/org/mycore/common/config/MCRInstantiatorPropertyListTest.java @@ -69,7 +69,7 @@ * not set * yes * not set - * Exception + * [] * Exception * * @@ -255,14 +255,13 @@ void test(boolean required, ValueProperty valueProperty, boolean defaultValue, D exception = e; } - boolean missingDefaultConfiguration = false; - missingDefaultConfiguration |= valueProperty.notSet() && defaultValue && defaultProperty.notSet(); - missingDefaultConfiguration |= required && valueProperty.notSet() && defaultValue && defaultProperty.setEmpty(); + boolean missingDefaultConfiguration = required && valueProperty.notSet() + && defaultValue && defaultProperty.notSetOrSetEmpty(); // all the indications a nested property should not be created (or creation should be suppressed) boolean shouldNotCreateProperty = false; shouldNotCreateProperty |= valueProperty.notSet() && !defaultValue; - shouldNotCreateProperty |= valueProperty.notSet() && defaultValue && defaultProperty.setEmpty(); + shouldNotCreateProperty |= valueProperty.notSet() && defaultValue && defaultProperty.notSetOrSetEmpty(); shouldNotCreateProperty |= valueProperty.setEmpty(); if (missingDefaultConfiguration) { @@ -348,12 +347,8 @@ enum DefaultProperty { SET_LONG_FORM; - public boolean notSet() { - return this == NOT_SET; - } - - public boolean setEmpty() { - return this == SET_EMPTY; + public boolean notSetOrSetEmpty() { + return this == NOT_SET || this == SET_EMPTY; } } diff --git a/mycore-base/src/test/java/org/mycore/common/config/MCRInstantiatorPropertyMapTest.java b/mycore-base/src/test/java/org/mycore/common/config/MCRInstantiatorPropertyMapTest.java index 178cf6c1c1..758326a42b 100644 --- a/mycore-base/src/test/java/org/mycore/common/config/MCRInstantiatorPropertyMapTest.java +++ b/mycore-base/src/test/java/org/mycore/common/config/MCRInstantiatorPropertyMapTest.java @@ -69,7 +69,7 @@ * not set * yes * not set - * Exception + * {} * Exception * * @@ -258,14 +258,13 @@ void test(boolean required, ValueProperty valueProperty, exception = e; } - boolean missingDefaultConfiguration = false; - missingDefaultConfiguration |= valueProperty.notSet() && defaultValue && defaultProperty.notSet(); - missingDefaultConfiguration |= required && valueProperty.notSet() && defaultValue && defaultProperty.setEmpty(); + boolean missingDefaultConfiguration = required && valueProperty.notSet() + && defaultValue && defaultProperty.notSetOrSetEmpty(); // all the indications a nested property should not be created (or creation should be suppressed) boolean shouldNotCreateProperty = false; shouldNotCreateProperty |= valueProperty.notSet() && !defaultValue; - shouldNotCreateProperty |= valueProperty.notSet() && defaultValue && defaultProperty.setEmpty(); + shouldNotCreateProperty |= valueProperty.notSet() && defaultValue && defaultProperty.notSetOrSetEmpty(); shouldNotCreateProperty |= valueProperty.setEmpty(); if (missingDefaultConfiguration) { @@ -351,12 +350,8 @@ enum DefaultProperty { SET_LONG_FORM; - public boolean notSet() { - return this == NOT_SET; - } - - public boolean setEmpty() { - return this == SET_EMPTY; + public boolean notSetOrSetEmpty() { + return this == NOT_SET || this == SET_EMPTY; } } diff --git a/mycore-base/src/test/java/org/mycore/common/config/MCRInstantiatorPropertyTest.java b/mycore-base/src/test/java/org/mycore/common/config/MCRInstantiatorPropertyTest.java index 647ddd16ee..db67548426 100644 --- a/mycore-base/src/test/java/org/mycore/common/config/MCRInstantiatorPropertyTest.java +++ b/mycore-base/src/test/java/org/mycore/common/config/MCRInstantiatorPropertyTest.java @@ -66,7 +66,7 @@ * not set * yes * not set - * Exception + * null * Exception * * @@ -193,10 +193,12 @@ void test(boolean required, ValueProperty valueProperty, exception = e; } - boolean missingDefaultConfiguration = valueProperty.notSet() && defaultValue && defaultProperty.notSet(); + boolean missingDefaultConfiguration = required && valueProperty.notSet() + && defaultValue && defaultProperty.notSet(); // all the indications a nested property should not be created (or creation should be suppressed) - boolean shouldNotCreateProperty = valueProperty.notSet() && !defaultValue; + boolean shouldNotCreateProperty = valueProperty.notSet() && + (!defaultValue || (!required && defaultProperty.notSet())); if (missingDefaultConfiguration) { From 88031e9a2f9f5db365a8e68049cd7b66c3189b20 Mon Sep 17 00:00:00 2001 From: Torsten Krause Date: Wed, 22 Jul 2026 15:27:21 +0200 Subject: [PATCH 3/6] MCR-3785 add method that provides all non-absent properties --- .../common/config/MCRConfiguration2.java | 33 ++++++++++++++++++- .../config/MCRPropertiesURIResolver.java | 2 +- .../MCRInstanceConfiguration.java | 18 +++++----- .../common/xsl/MCRParameterCollector.java | 2 +- .../validator/MCREditorOutValidator.java | 2 +- .../frontend/cli/MCRCommandLineInterface.java | 2 +- .../frontend/cli/MCRCommandManager.java | 2 +- .../frontend/cli/MCRDeveloperCommands.java | 4 +-- .../frontend/cli/MCRObjectCommands.java | 2 +- .../MCRObjectStaticContentGenerator.java | 2 +- .../MCRInstantiatorImplicitListTest.java | 2 +- .../MCRInstantiatorImplicitMapTest.java | 2 +- .../config/MCRInstantiatorImplicitTest.java | 2 +- .../MCRInstantiatorNonImplicitListTest.java | 2 +- .../MCRInstantiatorNonImplicitMapTest.java | 2 +- .../MCRInstantiatorNonImplicitTest.java | 2 +- .../MCRInstantiatorPropertyListTest.java | 2 +- .../MCRInstantiatorPropertyMapTest.java | 2 +- .../config/MCRInstantiatorPropertyTest.java | 2 +- .../config/MCRPropertiesURIResolverTest.java | 6 ++-- .../java/org/mycore/oai/MCROAIIdentify.java | 4 +-- .../MCRClassificationAndSetMapper.java | 4 +-- .../packaging/MCRPackerJobAction.java | 2 +- .../mycore/pi/MCRPIConfigurationChecker.java | 2 +- .../main/java/org/mycore/sword/MCRSword.java | 2 +- 25 files changed, 69 insertions(+), 38 deletions(-) diff --git a/mycore-base/src/main/java/org/mycore/common/config/MCRConfiguration2.java b/mycore-base/src/main/java/org/mycore/common/config/MCRConfiguration2.java index f13d836903..4b4bd761c0 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/MCRConfiguration2.java +++ b/mycore-base/src/main/java/org/mycore/common/config/MCRConfiguration2.java @@ -25,6 +25,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; @@ -107,12 +108,41 @@ public class MCRConfiguration2 { .softValues() .build(); + private static final AtomicReference> PROPERTIES = new AtomicReference<>(null); + static Map instanceHolder = new MCRConcurrentHashMap<>(); + /** + * @deprecated Use {@link #getRawProperties()} instead. + */ + @Deprecated public static Map getPropertiesMap() { + return getRawProperties(); + } + + /** + * Returns all configuration properties, including properties with empty values, i.e. + * properties that are not recognized by, for example, {@link #getString(String)}. + */ + public static Map getRawProperties() { return Collections.unmodifiableMap(MCRConfigurationBase.getResolvedProperties().getAsMap()); } + /** + * Returns all configuration properties, excluding properties with empty values, i.e. + * only properties that are recognized by, for example, {@link #getString(String)}. + */ + public static Map getAllProperties() { + return PROPERTIES.updateAndGet(existingValue -> { + if (existingValue != null) { + return existingValue; + } + return getRawProperties().entrySet().stream() + .filter(entry -> !entry.getValue().isBlank()) + .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().trim())); + }); + } + /** * Returns a sub map of properties where key is transformed. * @@ -135,7 +165,7 @@ public static Map getPropertiesMap() { * @return a map of the properties as stated above */ public static Map getSubPropertiesMap(String propertyPrefix) { - return getPropertiesMap() + return getRawProperties() .entrySet() .stream() .filter(e -> e.getKey().startsWith(propertyPrefix)) @@ -494,6 +524,7 @@ private static Class getClassObject(String classname) { } static void clearCaches() { + PROPERTIES.set(null); CONFIGURATIONS.invalidateAll(); } diff --git a/mycore-base/src/main/java/org/mycore/common/config/MCRPropertiesURIResolver.java b/mycore-base/src/main/java/org/mycore/common/config/MCRPropertiesURIResolver.java index 4b81db3a72..25178aa764 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/MCRPropertiesURIResolver.java +++ b/mycore-base/src/main/java/org/mycore/common/config/MCRPropertiesURIResolver.java @@ -105,7 +105,7 @@ private JDOMSource resolveKey(String key) { final Element entryElement = new Element("entry"); entryElement.setAttribute("key", key); - String value = MCRConfiguration2.getPropertiesMap().get(key); + String value = MCRConfiguration2.getRawProperties().get(key); if (value != null) { entryElement.setText(value); } diff --git a/mycore-base/src/main/java/org/mycore/common/config/instantiator/MCRInstanceConfiguration.java b/mycore-base/src/main/java/org/mycore/common/config/instantiator/MCRInstanceConfiguration.java index a397e7c1ca..8ddd4fd6fd 100644 --- a/mycore-base/src/main/java/org/mycore/common/config/instantiator/MCRInstanceConfiguration.java +++ b/mycore-base/src/main/java/org/mycore/common/config/instantiator/MCRInstanceConfiguration.java @@ -31,7 +31,7 @@ import org.mycore.common.config.MCRConfigurationException; /** - * Represents an extract of properties (typically {@link MCRConfiguration2#getPropertiesMap()}) used to + * Represents an extract of properties (typically {@link MCRConfiguration2#getRawProperties()}) used to * instantiate an object. Provides methods to extract nested configurations. *

* Generally speaking, a configuration has a {@link MCRInstanceConfiguration#name()} that represents the @@ -85,11 +85,11 @@ public S instantiate() { /** * Shorthand for {@link MCRInstanceConfiguration#ofClass(Class, Class, String, Map)} that * uses {@link MCRClassTools#forName(String)} to resolve the value class and - * uses {@link MCRConfiguration2#getPropertiesMap()} as the properties. + * uses {@link MCRConfiguration2#getRawProperties()} as the properties. */ public static MCRInstanceConfiguration ofClassName(Class superClass, String className, String prefix) { - return ofClassName(superClass, className, prefix, MCRConfiguration2.getPropertiesMap()); + return ofClassName(superClass, className, prefix, MCRConfiguration2.getRawProperties()); } /** @@ -108,11 +108,11 @@ public static MCRInstanceConfiguration ofClassName(Class superClass, S /** * Shorthand for {@link MCRInstanceConfiguration#ofClass(Class, Class, String, Map)} that - * uses {@link MCRConfiguration2#getPropertiesMap()} as the properties. + * uses {@link MCRConfiguration2#getRawProperties()} as the properties. */ public static MCRInstanceConfiguration ofClass(Class superClass, Class valueClass, String prefix) { - return ofClass(superClass, valueClass, prefix, MCRConfiguration2.getPropertiesMap()); + return ofClass(superClass, valueClass, prefix, MCRConfiguration2.getRawProperties()); } /** @@ -143,11 +143,11 @@ public static MCRInstanceConfiguration ofClass(Class superClass, Class /** * Shorthand for {@link MCRInstanceConfiguration#ofName(Class, MCRInstanceName, Map, Set)} that * creates the name with {@link MCRInstanceName#of(String)} and - * uses {@link MCRConfiguration2#getPropertiesMap()} as the properties and + * uses {@link MCRConfiguration2#getRawProperties()} as the properties and * uses {@link Options#NONE} as the options. */ public static MCRInstanceConfiguration ofName(Class superClass, String name) { - return ofName(superClass, MCRInstanceName.of(name), MCRConfiguration2.getPropertiesMap(), Options.NONE); + return ofName(superClass, MCRInstanceName.of(name), MCRConfiguration2.getRawProperties(), Options.NONE); } /** @@ -163,11 +163,11 @@ public static MCRInstanceConfiguration ofName(Class superClass, String /** * Shorthand for {@link MCRInstanceConfiguration#ofName(Class, MCRInstanceName, Map, Set)} that * creates the name with {@link MCRInstanceName#of(String)} and - * uses {@link MCRConfiguration2#getPropertiesMap()} as the properties. + * uses {@link MCRConfiguration2#getRawProperties()} as the properties. */ public static MCRInstanceConfiguration ofName(Class superClass, String name, Set

* Generally speaking, a configuration has a {@link MCRInstanceConfiguration#name()} that represents the @@ -66,12 +67,12 @@ public final class MCRInstanceConfiguration { private MCRInstanceConfiguration(Class superClass, Class valueClass, MCRInstanceName name, Map properties, Map fullProperties) { + properties.remove(CLASS_KEY); this.superClass = superClass; this.valueClass = valueClass; this.name = name; - this.properties = properties; + this.properties = Collections.unmodifiableMap(properties); this.fullProperties = fullProperties; - properties.remove(CLASS_KEY); } public boolean instantiatable() { @@ -85,11 +86,11 @@ public S instantiate() { /** * Shorthand for {@link MCRInstanceConfiguration#ofClass(Class, Class, String, Map)} that * uses {@link MCRClassTools#forName(String)} to resolve the value class and - * uses {@link MCRConfiguration2#getRawProperties()} as the properties. + * uses {@link MCRConfiguration2#getAllProperties()} as the properties. */ public static MCRInstanceConfiguration ofClassName(Class superClass, String className, String prefix) { - return ofClassName(superClass, className, prefix, MCRConfiguration2.getRawProperties()); + return ofClassName(superClass, className, prefix, MCRConfiguration2.getAllProperties()); } /** @@ -108,11 +109,11 @@ public static MCRInstanceConfiguration ofClassName(Class superClass, S /** * Shorthand for {@link MCRInstanceConfiguration#ofClass(Class, Class, String, Map)} that - * uses {@link MCRConfiguration2#getRawProperties()} as the properties. + * uses {@link MCRConfiguration2#getAllProperties()} as the properties. */ public static MCRInstanceConfiguration ofClass(Class superClass, Class valueClass, String prefix) { - return ofClass(superClass, valueClass, prefix, MCRConfiguration2.getRawProperties()); + return ofClass(superClass, valueClass, prefix, MCRConfiguration2.getAllProperties()); } /** @@ -143,11 +144,11 @@ public static MCRInstanceConfiguration ofClass(Class superClass, Class /** * Shorthand for {@link MCRInstanceConfiguration#ofName(Class, MCRInstanceName, Map, Set)} that * creates the name with {@link MCRInstanceName#of(String)} and - * uses {@link MCRConfiguration2#getRawProperties()} as the properties and + * uses {@link MCRConfiguration2#getAllProperties()} as the properties and * uses {@link Options#NONE} as the options. */ public static MCRInstanceConfiguration ofName(Class superClass, String name) { - return ofName(superClass, MCRInstanceName.of(name), MCRConfiguration2.getRawProperties(), Options.NONE); + return ofName(superClass, MCRInstanceName.of(name), MCRConfiguration2.getAllProperties(), Options.NONE); } /** @@ -163,11 +164,11 @@ public static MCRInstanceConfiguration ofName(Class superClass, String /** * Shorthand for {@link MCRInstanceConfiguration#ofName(Class, MCRInstanceName, Map, Set)} that * creates the name with {@link MCRInstanceName#of(String)} and - * uses {@link MCRConfiguration2#getRawProperties()} as the properties. + * uses {@link MCRConfiguration2#getAllProperties()} as the properties. */ public static MCRInstanceConfiguration ofName(Class superClass, String name, Set