diff --git a/app/src/main/java/ch/sbb/polarion/extension/generic/properties/ExtendedProperties.java b/app/src/main/java/ch/sbb/polarion/extension/generic/properties/ExtendedProperties.java new file mode 100644 index 0000000..d94ca1d --- /dev/null +++ b/app/src/main/java/ch/sbb/polarion/extension/generic/properties/ExtendedProperties.java @@ -0,0 +1,59 @@ +package ch.sbb.polarion.extension.generic.properties; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Properties; + +public class ExtendedProperties extends Properties { + + private final HashMap defaultValues = new HashMap<>(); + private final HashMap descriptions = new HashMap<>(); + + public ExtendedProperties() { + super(); + } + + public ExtendedProperties(int size) { + super(size); + } + + @Override + public synchronized Object setProperty(@NotNull String key, @NotNull String value) { + return this.setProperty(key, value, null, null); + } + + public synchronized Object setProperty(@NotNull String key, @NotNull String value, @Nullable String defaultValue, @Nullable String description) { + return this.put(key, value, defaultValue, description); + } + + public synchronized Object put(@NotNull Object key, @NotNull Object value, @Nullable String defaultValue, @Nullable String description) { + defaultValues.put(key, defaultValue); + descriptions.put(key, description); + return super.put(key, value); + } + + public synchronized @Nullable String getDescription(@NotNull Object key) { + return descriptions.get(key); + } + + public synchronized @Nullable String getDefaultValue(@NotNull Object key) { + return defaultValues.get(key); + } + + @Override + public synchronized Object put(@NotNull Object key, @NotNull Object value) { + return this.put(key, value, null, null); + } + + @Override + public synchronized boolean equals(Object o) { + return super.equals(o); + } + + @Override + public synchronized int hashCode() { + return super.hashCode(); + } +} diff --git a/app/src/main/java/ch/sbb/polarion/extension/generic/properties/ExtensionConfiguration.java b/app/src/main/java/ch/sbb/polarion/extension/generic/properties/ExtensionConfiguration.java index 615d708..352caa3 100644 --- a/app/src/main/java/ch/sbb/polarion/extension/generic/properties/ExtensionConfiguration.java +++ b/app/src/main/java/ch/sbb/polarion/extension/generic/properties/ExtensionConfiguration.java @@ -4,21 +4,33 @@ import com.polarion.core.config.impl.SystemValueReader; import lombok.Getter; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.List; -import java.util.Properties; @Getter public class ExtensionConfiguration implements IExtensionConfiguration { private static final String DEBUG = "debug"; + public static final boolean DEBUG_DEFAULT_VALUE = false; + private static final String DEBUG_DESCRIPTION = "Enable debug mode for current extension"; @NotNull protected final String propertyPrefix; - @Override + @SuppressWarnings("unused") public boolean isDebug() { - return SystemValueReader.getInstance().readBoolean(propertyPrefix + DEBUG, false); + return SystemValueReader.getInstance().readBoolean(propertyPrefix + DEBUG, DEBUG_DEFAULT_VALUE); + } + + @SuppressWarnings("unused") + public String getDebugDescription() { + return DEBUG_DESCRIPTION; + } + + @SuppressWarnings("unused") + public Boolean getDebugDefault() { + return DEBUG_DEFAULT_VALUE; } protected ExtensionConfiguration() { @@ -26,16 +38,18 @@ protected ExtensionConfiguration() { } @Override - public final @NotNull Properties getProperties() { + public final @NotNull ExtendedProperties getProperties() { List supportedProperties = getSupportedProperties(); - Properties properties = new Properties(supportedProperties.size()); + ExtendedProperties properties = new ExtendedProperties(supportedProperties.size()); ExtensionConfiguration configuration = CurrentExtensionConfiguration.getInstance().getExtensionConfiguration(); for (String supportedProperty : supportedProperties) { - String key = getPropertyPrefix() + supportedProperty; - String value = GetterFinder.getValue(configuration, supportedProperty); + @NotNull String key = getPropertyPrefix() + supportedProperty; + @Nullable String value = GetterFinder.getValue(configuration, supportedProperty); + @Nullable String defaultValue = GetterFinder.getDefaultValue(configuration, supportedProperty); + @Nullable String description = GetterFinder.getDescription(configuration, supportedProperty); if (value != null) { - properties.setProperty(key, value); + properties.setProperty(key, value, defaultValue, description); } } diff --git a/app/src/main/java/ch/sbb/polarion/extension/generic/properties/GetterFinder.java b/app/src/main/java/ch/sbb/polarion/extension/generic/properties/GetterFinder.java index 333f4a0..e347fe5 100644 --- a/app/src/main/java/ch/sbb/polarion/extension/generic/properties/GetterFinder.java +++ b/app/src/main/java/ch/sbb/polarion/extension/generic/properties/GetterFinder.java @@ -9,27 +9,42 @@ @UtilityClass public class GetterFinder { - @SuppressWarnings({"squid:S1166", "findbugs:REC_CATCH_EXCEPTION"}) // no need to log or rethrow exception by design, findbugs wrongly assumes that getClass().getMethod() won't throw exceptions public static @Nullable String getValue(@NotNull T extensionConfiguration, @NotNull String propertyName) { - String getterMethodName = toCamelCaseGetter(propertyName); + return invokeGetter(extensionConfiguration, propertyName, ""); + } - try { - Method getter = extensionConfiguration.getClass().getMethod("get" + getterMethodName); - return String.valueOf(getter.invoke(extensionConfiguration)); - } catch (Exception ignored) { - //ignore + public static @Nullable String getDescription(@NotNull T extensionConfiguration, @NotNull String propertyName) { + return invokeGetter(extensionConfiguration, propertyName, "Description"); + } + + public static @Nullable String getDefaultValue(@NotNull T extensionConfiguration, @NotNull String propertyName) { + return invokeGetter(extensionConfiguration, propertyName, "DefaultValue"); + } + + private static @Nullable String invokeGetter(@NotNull T extensionConfiguration, @NotNull String propertyName, @NotNull String suffix) { + String getterMethodName = toCamelCaseGetter(propertyName) + suffix; + + // try "get" prefix + String result = invokeMethod(extensionConfiguration, "get" + getterMethodName); + if (result != null) { + return result; } + // try "is" prefix for boolean types + return invokeMethod(extensionConfiguration, "is" + getterMethodName); + } + + @SuppressWarnings({"squid:S1166", "findbugs:REC_CATCH_EXCEPTION"}) // no need to log or rethrow exception by design, findbugs wrongly assumes that getClass().getMethod() won't throw exceptions + private static @Nullable String invokeMethod(@NotNull T extensionConfiguration, @NotNull String methodName) { try { - Method getter = extensionConfiguration.getClass().getMethod("is" + getterMethodName); - if (getter.getReturnType() == boolean.class) { - return String.valueOf(getter.invoke(extensionConfiguration)); + Method getter = extensionConfiguration.getClass().getMethod(methodName); + if (methodName.startsWith("is") && getter.getReturnType() != boolean.class) { + return null; } + return String.valueOf(getter.invoke(extensionConfiguration)); } catch (Exception ignored) { - //ignore + return null; } - - return null; } private static @NotNull String toCamelCaseGetter(@NotNull String propertyName) { diff --git a/app/src/main/java/ch/sbb/polarion/extension/generic/properties/IExtensionConfiguration.java b/app/src/main/java/ch/sbb/polarion/extension/generic/properties/IExtensionConfiguration.java index e7309f6..3cd224d 100644 --- a/app/src/main/java/ch/sbb/polarion/extension/generic/properties/IExtensionConfiguration.java +++ b/app/src/main/java/ch/sbb/polarion/extension/generic/properties/IExtensionConfiguration.java @@ -3,13 +3,10 @@ import org.jetbrains.annotations.NotNull; import java.util.List; -import java.util.Properties; public interface IExtensionConfiguration { - boolean isDebug(); - - @NotNull Properties getProperties(); + @NotNull ExtendedProperties getProperties(); @NotNull List getSupportedProperties(); diff --git a/app/src/main/resources/META-INF/resources/common/jsp/about.jsp b/app/src/main/resources/META-INF/resources/common/jsp/about.jsp index 7915b02..f221aba 100644 --- a/app/src/main/resources/META-INF/resources/common/jsp/about.jsp +++ b/app/src/main/resources/META-INF/resources/common/jsp/about.jsp @@ -1,30 +1,31 @@ +<%@ page import="ch.sbb.polarion.extension.generic.configuration.ConfigurationStatus" %> +<%@ page import="ch.sbb.polarion.extension.generic.configuration.ConfigurationStatusProvider" %> <%@ page import="ch.sbb.polarion.extension.generic.properties.CurrentExtensionConfiguration" %> +<%@ page import="ch.sbb.polarion.extension.generic.properties.ExtendedProperties" %> +<%@ page import="ch.sbb.polarion.extension.generic.rest.model.Context" %> <%@ page import="ch.sbb.polarion.extension.generic.rest.model.Version" %> <%@ page import="ch.sbb.polarion.extension.generic.util.ExtensionInfo" %> <%@ page import="ch.sbb.polarion.extension.generic.util.VersionUtils" %> -<%@ page import="java.util.Collections" %> -<%@ page import="java.util.Properties" %> +<%@ page import="org.jetbrains.annotations.Nullable" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.nio.charset.StandardCharsets" %> -<%@ page import="java.util.List" %> -<%@ page import="java.util.Set" %> <%@ page import="java.util.ArrayList" %> -<%@ page import="ch.sbb.polarion.extension.generic.rest.model.Context" %> -<%@ page import="ch.sbb.polarion.extension.generic.configuration.ConfigurationStatus" %> -<%@ page import="ch.sbb.polarion.extension.generic.configuration.ConfigurationStatusProvider" %> <%@ page import="java.util.Collection" %> +<%@ page import="java.util.Collections" %> +<%@ page import="java.util.List" %> +<%@ page import="java.util.Set" %> <%@ page contentType="text/html; charset=UTF-8" %> <%! private static final String ABOUT_TABLE_ROW = "%s%s"; - private static final String CONFIGURATION_PROPERTIES_TABLE_ROW = "%s%s"; + private static final String CONFIGURATION_PROPERTIES_TABLE_ROW = "%s%s%s%s"; private static final String CHECK_CONFIGURATION_TABLE_ROW = "%s%s%s"; Context context = ExtensionInfo.getInstance().getContext(); Version version = ExtensionInfo.getInstance().getVersion(); - Properties properties = CurrentExtensionConfiguration.getInstance().getExtensionConfiguration().getProperties(); + ExtendedProperties extensionConfigurationProperties = CurrentExtensionConfiguration.getInstance().getExtensionConfiguration().getProperties(); %> @@ -72,11 +73,13 @@ Configuration property Value + Default + Description <% - Set keySet = properties.keySet(); + Set keySet = extensionConfigurationProperties.keySet(); List propertyNames = new ArrayList<>(); for (Object key : keySet) { propertyNames.add((String) key); @@ -84,8 +87,10 @@ Collections.sort(propertyNames); for (String key : propertyNames) { - String value = properties.getProperty(key); - String row = CONFIGURATION_PROPERTIES_TABLE_ROW.formatted(key, value); + @Nullable String value = extensionConfigurationProperties.getProperty(key); + @Nullable String defaultValue = extensionConfigurationProperties.getDefaultValue(key); + @Nullable String description = extensionConfigurationProperties.getDescription(key); + String row = CONFIGURATION_PROPERTIES_TABLE_ROW.formatted(key, value, defaultValue == null ? "" : defaultValue, description == null ? "" : description); out.println(row); } %> diff --git a/app/src/test/java/ch/sbb/polarion/extension/generic/properties/ExtendedPropertiesTest.java b/app/src/test/java/ch/sbb/polarion/extension/generic/properties/ExtendedPropertiesTest.java new file mode 100644 index 0000000..85e8354 --- /dev/null +++ b/app/src/test/java/ch/sbb/polarion/extension/generic/properties/ExtendedPropertiesTest.java @@ -0,0 +1,82 @@ +package ch.sbb.polarion.extension.generic.properties; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ExtendedPropertiesTest { + + private ExtendedProperties props; + + @BeforeEach + void setUp() { + props = new ExtendedProperties(); + } + + @Test + void testSetPropertyWithDescriptionAndDefaultValue() { + props.setProperty("username", "admin", "root", "User for admin tasks"); + + assertEquals("admin", props.getProperty("username")); + assertEquals("User for admin tasks", props.getDescription("username")); + assertEquals("root", props.getDefaultValue("username")); + } + + @Test + void testSetPropertyWithoutDescriptionAndDefaultValue() { + props.setProperty("language", "EN"); + + assertEquals("EN", props.getProperty("language")); + assertNull(props.getDescription("language")); + assertNull(props.getDefaultValue("language")); + } + + @Test + void testOverridePropertyKeepsNewDescriptionAndDefaultValue() { + props.setProperty("timeout", "30", "60", "Timeout in seconds"); + props.setProperty("timeout", "45", "90", "Updated timeout description"); + + assertEquals("45", props.getProperty("timeout")); + assertEquals("Updated timeout description", props.getDescription("timeout")); + assertEquals("90", props.getDefaultValue("timeout")); + } + + @Test + void testNullDescriptionAndDefaultValueHandledProperly() { + props.setProperty("color", "blue", null, null); + + assertEquals("blue", props.getProperty("color")); + assertNull(props.getDescription("color")); + assertNull(props.getDefaultValue("color")); + } + + @Test + void testPutMethodWithDescriptionAndDefaultValue() { + props.put("fontSize", "12", "10", "Size of font in points"); + + assertEquals("12", props.getProperty("fontSize")); + assertEquals("Size of font in points", props.getDescription("fontSize")); + assertEquals("10", props.getDefaultValue("fontSize")); + } + + @Test + void testPutMethodWithoutDescriptionAndDefaultValue() { + props.put("theme", "dark"); + + assertEquals("dark", props.getProperty("theme")); + assertNull(props.getDescription("theme")); + assertNull(props.getDefaultValue("theme")); + } + + @Test + void testOverrideWithNullValues() { + props.setProperty("path", "/home/user", "File path", "/root"); + props.setProperty("path", "/home/new_user", null, null); + + assertEquals("/home/new_user", props.getProperty("path")); + assertNull(props.getDescription("path")); + assertNull(props.getDefaultValue("path")); + } + +}