Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.function.Supplier;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -49,26 +50,34 @@ public class SystemPropertySupplier<T> implements Supplier<T> {
private final String propName;
private final T defaultValue;
private final Function<String, T> parser;

private Logger log = LOG;
private String successLogLevel = "INFO";
private boolean hideValue = false;
private Predicate<T> validator = (a) -> true;
private Predicate<T> validator = a -> true;
private Function<String, String> sysPropReader = System::getProperty;
private BiFunction<String, T, String> setMessageFormatter = (a, b) -> String.format("System property %s found to be '%s'", a,
hideValue ? HIDDEN_REPLACEMENT : b);

private SystemPropertySupplier(@NotNull String propName, @NotNull T defaultValue) {
private SystemPropertySupplier(@NotNull String propName, @Nullable T defaultValue, @Nullable Class<T> clazz) {
this.propName = Objects.requireNonNull(propName, "propertyName must be non-null");
this.defaultValue = Objects.requireNonNull(defaultValue, "defaultValue must be non-null");
this.parser = getValueParser(defaultValue);
this.defaultValue = defaultValue;
this.parser = getValueParser(defaultValue, clazz);
}

/**
* Create it for a given property name and default value.
*/
public static <U> SystemPropertySupplier<U> create(@NotNull String propName, @NotNull U defaultValue) {
return new SystemPropertySupplier<>(propName, defaultValue);
return new SystemPropertySupplier<>(propName,
Objects.requireNonNull(defaultValue, "defaultValue must not be null"), null);
}

/**
* Create it for a given property name and no default value (but expected {@linkplain Class}).
*/
public static <U> SystemPropertySupplier<U> create(@NotNull String propName, @NotNull Class<U> clazz) {
return new SystemPropertySupplier<>(propName, null,
Objects.requireNonNull(clazz, "clazz must not be null"));
}

/**
Expand Down Expand Up @@ -100,18 +109,10 @@ public SystemPropertySupplier<T> formatSetMessage(@NotNull BiFunction<String, T,
* Specify logging level to use for "success" message (defaults to "INFO")
*/
public SystemPropertySupplier<T> logSuccessAs(String successLogLevel) {
String newLevel;
switch (Objects.requireNonNull(successLogLevel)) {
case "DEBUG":
case "ERROR":
case "INFO":
case "TRACE":
case "WARN":
newLevel = successLogLevel;
break;
default:
throw new IllegalArgumentException("unsupported log level: " + successLogLevel);
}
String newLevel = switch (Objects.requireNonNull(successLogLevel)) {
case "DEBUG", "ERROR", "INFO", "TRACE", "WARN" -> successLogLevel;
default -> throw new IllegalArgumentException("unsupported log level: " + successLogLevel);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #2995, we are removing this exception, so I would avoid changing this method.

};
this.successLogLevel = newLevel;
return this;
}
Expand Down Expand Up @@ -163,7 +164,7 @@ public T get() {
log.error("Ignoring malformed value '{}' for system property {}", displayedValue, propName);
}

if (!returnValue.equals(defaultValue)) {
if (!Objects.equals(returnValue, defaultValue)) {
String msg = setMessageFormatter.apply(propName, returnValue);
switch (successLogLevel) {
case "INFO":
Expand Down Expand Up @@ -191,18 +192,21 @@ public T get() {
}

@SuppressWarnings("unchecked")
private static <T> Function<String, T> getValueParser(T defaultValue) {
if (defaultValue instanceof Boolean) {
private static <T> Function<String, T> getValueParser(T defaultValue, Class<T> type) {
Class<?> clazz = (defaultValue != null) ? defaultValue.getClass() : type;

if (clazz == null) {
throw new IllegalArgumentException("Cannot determine type: defaultValue is null and no type provided.");
} else if (Boolean.class.isAssignableFrom(clazz)) {
return v -> (T) Boolean.valueOf(v);
} else if (defaultValue instanceof Integer) {
} else if (Integer.class.isAssignableFrom(clazz)) {
return v -> (T) Integer.valueOf(v);
} else if (defaultValue instanceof Long) {
} else if (Long.class.isAssignableFrom(clazz)) {
return v -> (T) Long.valueOf(v);
} else if (defaultValue instanceof String) {
} else if (String.class.isAssignableFrom(clazz)) {
return v -> (T) v;
} else {
throw new IllegalArgumentException(
String.format("expects a defaultValue of Boolean, Integer, Long, or String, but got: %s", defaultValue.getClass()));
throw new IllegalArgumentException("Unsupported type: " + clazz.getName());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* <em>For Oak internal use only. Do not use outside Oak components.</em>
*/
@Internal(since = "1.1.0")
@Version("2.1.0")
@Version("2.2.0")
package org.apache.jackrabbit.oak.commons.properties;

import org.apache.jackrabbit.oak.commons.annotations.Internal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.apache.jackrabbit.oak.commons.properties;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import org.apache.jackrabbit.oak.commons.junit.LogCustomizer;
Expand Down Expand Up @@ -137,4 +138,38 @@ public void testUnsupportedType() {
} catch (IllegalArgumentException expected) {
}
}

@Test
public void testCheckNullDefault() {
try {
assertNull(SystemPropertySupplier.create("foo", Boolean.class).
usingSystemPropertyReader((n) -> null).get());
} catch (IllegalArgumentException expected) {
Comment on lines +144 to +147

@rishabhdaim rishabhdaim Jul 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would re-write this test to use @Test(expected=....) pattern rather than try/catch.
and add Assert.fail() after the test so that it does test that exception is thrown.

}
}

@Test
public void testCheckNoDefaultNotSet() {
assertNull(SystemPropertySupplier.create("foo", Boolean.class).
usingSystemPropertyReader((n) -> null).get());
}

@Test
public void testCheckNoDefaultSet() {
int value = SystemPropertySupplier.create("foo", Integer.class).
usingSystemPropertyReader((n) -> "4217").get();
assertEquals(4217, value);
}

@Test
public void testCheckNoDefaultSetInvalid() {
assertNull(SystemPropertySupplier.create("foo", Integer.class).
usingSystemPropertyReader((n) -> "abcd").get());
}

@Test
public void testCheckNoDefaultValidatorRejects() {
assertNull(SystemPropertySupplier.create("foo", String.class).
usingSystemPropertyReader((n) -> "abcd").validateWith(x-> false) .get());
}
}
Loading