From 1e210a2eed71b3a214dd72103aed0fef737be3e0 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Wed, 10 Jun 2026 14:28:11 +0100 Subject: [PATCH 1/7] OAK-12253: add a mechanism to override FT defaults via system property --- .../org/apache/jackrabbit/oak/spi/toggle/Feature.java | 8 +++++++- .../jackrabbit/oak/spi/toggle/FeatureToggleTest.java | 11 +++++++++++ oak-core-spi/src/test/resources/logback-test.xml | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java index afad73f2e6f..7fe362365a1 100644 --- a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java +++ b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java @@ -22,8 +22,11 @@ import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.jackrabbit.oak.commons.properties.SystemPropertySupplier; import org.apache.jackrabbit.oak.spi.whiteboard.Registration; import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A feature toggle to control new functionality. The default state of a feature @@ -40,6 +43,7 @@ */ public final class Feature implements Closeable { + private static final Logger log = LoggerFactory.getLogger(Feature.class); private final AtomicBoolean value; private final Registration registration; @@ -60,7 +64,9 @@ private Feature(AtomicBoolean value, Registration registration) { * @return the feature toggle. */ public static Feature newFeature(String name, Whiteboard whiteboard) { - AtomicBoolean value = new AtomicBoolean(); + // by default the initial value is false, but it can be overridden by a system property + AtomicBoolean value = new AtomicBoolean( + SystemPropertySupplier.create("oak-feature." + name, false).logSuccessAs("INFO").get()); FeatureToggle adapter = new FeatureToggle(name, value); return new Feature(value, whiteboard.register( FeatureToggle.class, adapter, Collections.emptyMap())); diff --git a/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java b/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java index 7999b04f521..803b1c13a58 100644 --- a/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java +++ b/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java @@ -50,6 +50,17 @@ public void disabledByDefault() { } } + @Test + public void defaultOverridden() { + String sysPropName = "oak-feature.my.toggle"; + System.setProperty(sysPropName, "true"); + try (Feature feature = newFeature("my.toggle", whiteboard)) { + assertTrue(feature.isEnabled()); + } finally { + System.clearProperty(sysPropName); + } + } + @Test public void register() { try (Feature feature = newFeature("my.toggle", whiteboard)) { diff --git a/oak-core-spi/src/test/resources/logback-test.xml b/oak-core-spi/src/test/resources/logback-test.xml index 042856c27b8..efce63c4182 100644 --- a/oak-core-spi/src/test/resources/logback-test.xml +++ b/oak-core-spi/src/test/resources/logback-test.xml @@ -27,7 +27,7 @@ - + From 98b38ac11765e7f6c0ea722152bd3a0ba2960a97 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Thu, 11 Jun 2026 09:30:30 +0100 Subject: [PATCH 2/7] OAK-12253: add a mechanism to override FT defaults via system property - temporary workaround for Sonar complaint) --- .../org/apache/jackrabbit/oak/spi/toggle/Feature.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java index 7fe362365a1..838a846e1c5 100644 --- a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java +++ b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java @@ -43,7 +43,6 @@ */ public final class Feature implements Closeable { - private static final Logger log = LoggerFactory.getLogger(Feature.class); private final AtomicBoolean value; private final Registration registration; @@ -64,9 +63,14 @@ private Feature(AtomicBoolean value, Registration registration) { * @return the feature toggle. */ public static Feature newFeature(String name, Whiteboard whiteboard) { + boolean b = false; // by default the initial value is false, but it can be overridden by a system property - AtomicBoolean value = new AtomicBoolean( - SystemPropertySupplier.create("oak-feature." + name, false).logSuccessAs("INFO").get()); + try { + b = SystemPropertySupplier.create("oak-feature." + name, false).get(); + } catch (IllegalArgumentException e) { + // https://issues.apache.org/jira/browse/OAK-12255 + } + AtomicBoolean value = new AtomicBoolean(b); FeatureToggle adapter = new FeatureToggle(name, value); return new Feature(value, whiteboard.register( FeatureToggle.class, adapter, Collections.emptyMap())); From 336e8978be44f1f2d7f93fa708ed593ec2735a0e Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Thu, 11 Jun 2026 09:41:47 +0100 Subject: [PATCH 3/7] Revert "OAK-12253: add a mechanism to override FT defaults via system property - temporary workaround for Sonar complaint)" This reverts commit 98b38ac11765e7f6c0ea722152bd3a0ba2960a97. --- .../org/apache/jackrabbit/oak/spi/toggle/Feature.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java index 838a846e1c5..7fe362365a1 100644 --- a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java +++ b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java @@ -43,6 +43,7 @@ */ public final class Feature implements Closeable { + private static final Logger log = LoggerFactory.getLogger(Feature.class); private final AtomicBoolean value; private final Registration registration; @@ -63,14 +64,9 @@ private Feature(AtomicBoolean value, Registration registration) { * @return the feature toggle. */ public static Feature newFeature(String name, Whiteboard whiteboard) { - boolean b = false; // by default the initial value is false, but it can be overridden by a system property - try { - b = SystemPropertySupplier.create("oak-feature." + name, false).get(); - } catch (IllegalArgumentException e) { - // https://issues.apache.org/jira/browse/OAK-12255 - } - AtomicBoolean value = new AtomicBoolean(b); + AtomicBoolean value = new AtomicBoolean( + SystemPropertySupplier.create("oak-feature." + name, false).logSuccessAs("INFO").get()); FeatureToggle adapter = new FeatureToggle(name, value); return new Feature(value, whiteboard.register( FeatureToggle.class, adapter, Collections.emptyMap())); From 502a6cbe7beb5e6731321af0fde83eed21eb997e Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Thu, 11 Jun 2026 09:45:50 +0100 Subject: [PATCH 4/7] OAK-12253: add a mechanism to override FT defaults via system property - avoid unneeded call that could cause IAE --- .../java/org/apache/jackrabbit/oak/spi/toggle/Feature.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java index 7fe362365a1..ed3ddfe9b26 100644 --- a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java +++ b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java @@ -43,7 +43,6 @@ */ public final class Feature implements Closeable { - private static final Logger log = LoggerFactory.getLogger(Feature.class); private final AtomicBoolean value; private final Registration registration; @@ -66,7 +65,7 @@ private Feature(AtomicBoolean value, Registration registration) { public static Feature newFeature(String name, Whiteboard whiteboard) { // by default the initial value is false, but it can be overridden by a system property AtomicBoolean value = new AtomicBoolean( - SystemPropertySupplier.create("oak-feature." + name, false).logSuccessAs("INFO").get()); + SystemPropertySupplier.create("oak-feature." + name, false).get()); FeatureToggle adapter = new FeatureToggle(name, value); return new Feature(value, whiteboard.register( FeatureToggle.class, adapter, Collections.emptyMap())); From 089193f112c9851eb76a6fb2619781ddc6ddb186 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Fri, 12 Jun 2026 08:15:22 +0100 Subject: [PATCH 5/7] OAK-12253: add a mechanism to override FT defaults via system property --- .../apache/jackrabbit/oak/spi/toggle/Feature.java | 12 ++++++++++-- .../oak/spi/toggle/FeatureToggleTest.java | 13 ++++++++++++- oak-core-spi/src/test/resources/logback-test.xml | 3 ++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java index ed3ddfe9b26..e5098428b09 100644 --- a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java +++ b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java @@ -37,12 +37,19 @@ * involves registering a feature toggle on the {@link Whiteboard} and * potentially comes with some overhead (e.g. when the whiteboard is based on * OSGi). Therefore, client code should not create a new feature, check the - * state and then immediately release/close it again. Instead a feature should + * state and then immediately release/close it again. Instead, a feature should * be acquired initially, checked at runtime whenever needed and finally * released when the client component is destroyed. + *

+ * The default state of {@code false} can be overridden by a system property, + * with the name derived from the toggle name. This helps to quickly verify + * a new feature. For a toggle named {@code "foo"}, the system property name is + * {@code oak-feature.foo}. */ public final class Feature implements Closeable { + private static final Logger LOG = LoggerFactory.getLogger(Feature.class); + private final AtomicBoolean value; private final Registration registration; @@ -65,7 +72,8 @@ private Feature(AtomicBoolean value, Registration registration) { public static Feature newFeature(String name, Whiteboard whiteboard) { // by default the initial value is false, but it can be overridden by a system property AtomicBoolean value = new AtomicBoolean( - SystemPropertySupplier.create("oak-feature." + name, false).get()); + SystemPropertySupplier.create("oak-feature." + name, false). + loggingTo(LOG).get()); FeatureToggle adapter = new FeatureToggle(name, value); return new Feature(value, whiteboard.register( FeatureToggle.class, adapter, Collections.emptyMap())); diff --git a/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java b/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java index 803b1c13a58..1b394890cc0 100644 --- a/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java +++ b/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java @@ -51,9 +51,20 @@ public void disabledByDefault() { } @Test - public void defaultOverridden() { + public void defaultOverriddenAsTrue() { String sysPropName = "oak-feature.my.toggle"; System.setProperty(sysPropName, "true"); + try (Feature feature = newFeature("my.toggle", whiteboard)) { + assertFalse(feature.isEnabled()); + } finally { + System.clearProperty(sysPropName); + } + } + + @Test + public void defaultOverriddenAsFalse() { + String sysPropName = "oak-feature.my.toggle"; + System.setProperty(sysPropName, "false"); try (Feature feature = newFeature("my.toggle", whiteboard)) { assertTrue(feature.isEnabled()); } finally { diff --git a/oak-core-spi/src/test/resources/logback-test.xml b/oak-core-spi/src/test/resources/logback-test.xml index efce63c4182..accccfcb4a0 100644 --- a/oak-core-spi/src/test/resources/logback-test.xml +++ b/oak-core-spi/src/test/resources/logback-test.xml @@ -27,11 +27,12 @@ - + + \ No newline at end of file From fd03ba947d891e3766c80dbfd6fbb069cededb50 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Fri, 12 Jun 2026 08:19:38 +0100 Subject: [PATCH 6/7] OAK-12253: add a mechanism to override FT defaults via system property --- .../apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java b/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java index 1b394890cc0..c49ce9b86da 100644 --- a/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java +++ b/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java @@ -55,7 +55,7 @@ public void defaultOverriddenAsTrue() { String sysPropName = "oak-feature.my.toggle"; System.setProperty(sysPropName, "true"); try (Feature feature = newFeature("my.toggle", whiteboard)) { - assertFalse(feature.isEnabled()); + assertTrue(feature.isEnabled()); } finally { System.clearProperty(sysPropName); } @@ -66,7 +66,7 @@ public void defaultOverriddenAsFalse() { String sysPropName = "oak-feature.my.toggle"; System.setProperty(sysPropName, "false"); try (Feature feature = newFeature("my.toggle", whiteboard)) { - assertTrue(feature.isEnabled()); + assertFalse(feature.isEnabled()); } finally { System.clearProperty(sysPropName); } From 1f772a25141b610f520892f368adefe931c818ea Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Fri, 12 Jun 2026 14:22:32 +0100 Subject: [PATCH 7/7] OAK-12253: add a mechanism to override FT defaults via system property - make it opt in --- .../jackrabbit/oak/spi/toggle/Feature.java | 31 +++++++++++++------ .../oak/spi/toggle/package-info.java | 2 +- .../oak/spi/toggle/FeatureToggleTest.java | 5 +-- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java index e5098428b09..7fdc1d2d06d 100644 --- a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java +++ b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/Feature.java @@ -41,8 +41,9 @@ * be acquired initially, checked at runtime whenever needed and finally * released when the client component is destroyed. *

- * The default state of {@code false} can be overridden by a system property, - * with the name derived from the toggle name. This helps to quickly verify + * The default state of {@code false} can be overridden by a system property + * using {@linkplain #newFeatureWithSystemPropertyDefault(String, Whiteboard)}, + * in which case The property name is derived from the toggle name. This helps to quickly verify * a new feature. For a toggle named {@code "foo"}, the system property name is * {@code oak-feature.foo}. */ @@ -59,6 +60,16 @@ private Feature(AtomicBoolean value, Registration registration) { this.registration = registration; } + private static Feature internalNewFeatureWithSystemPropertyDefault(String name, Whiteboard whiteboard, boolean withSysPropDefault) { + // by default the initial value is false, but it can be overridden by a system property + AtomicBoolean value = withSysPropDefault ? new AtomicBoolean( + SystemPropertySupplier.create("oak-feature." + name, false). + loggingTo(LOG).get()) : new AtomicBoolean(); + FeatureToggle adapter = new FeatureToggle(name, value); + return new Feature(value, whiteboard.register( + FeatureToggle.class, adapter, Collections.emptyMap())); + } + /** * Creates a new {@link Feature} with the given name and registers the * corresponding {@link FeatureToggle} on the {@link Whiteboard}. @@ -70,13 +81,15 @@ private Feature(AtomicBoolean value, Registration registration) { * @return the feature toggle. */ public static Feature newFeature(String name, Whiteboard whiteboard) { - // by default the initial value is false, but it can be overridden by a system property - AtomicBoolean value = new AtomicBoolean( - SystemPropertySupplier.create("oak-feature." + name, false). - loggingTo(LOG).get()); - FeatureToggle adapter = new FeatureToggle(name, value); - return new Feature(value, whiteboard.register( - FeatureToggle.class, adapter, Collections.emptyMap())); + return internalNewFeatureWithSystemPropertyDefault(name, whiteboard, false); + } + + /** + * Same as {@linkplain #newFeature(String, Whiteboard)}, but with the initial state provided + * by a system property, named based on the feature's name. + */ + public static Feature newFeatureWithSystemPropertyDefault(String name, Whiteboard whiteboard) { + return internalNewFeatureWithSystemPropertyDefault(name, whiteboard, true); } /** diff --git a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/package-info.java b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/package-info.java index 840e64c3fcb..c1a3042e113 100644 --- a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/package-info.java +++ b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/toggle/package-info.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -@Version("1.0.0") +@Version("1.1.0") package org.apache.jackrabbit.oak.spi.toggle; import org.osgi.annotation.versioning.Version; \ No newline at end of file diff --git a/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java b/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java index c49ce9b86da..8ffbe3990ca 100644 --- a/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java +++ b/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/toggle/FeatureToggleTest.java @@ -28,6 +28,7 @@ import org.junit.Test; import static org.apache.jackrabbit.oak.spi.toggle.Feature.newFeature; +import static org.apache.jackrabbit.oak.spi.toggle.Feature.newFeatureWithSystemPropertyDefault; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.Matchers.empty; import static org.hamcrest.core.Is.is; @@ -54,7 +55,7 @@ public void disabledByDefault() { public void defaultOverriddenAsTrue() { String sysPropName = "oak-feature.my.toggle"; System.setProperty(sysPropName, "true"); - try (Feature feature = newFeature("my.toggle", whiteboard)) { + try (Feature feature = newFeatureWithSystemPropertyDefault("my.toggle", whiteboard)) { assertTrue(feature.isEnabled()); } finally { System.clearProperty(sysPropName); @@ -65,7 +66,7 @@ public void defaultOverriddenAsTrue() { public void defaultOverriddenAsFalse() { String sysPropName = "oak-feature.my.toggle"; System.setProperty(sysPropName, "false"); - try (Feature feature = newFeature("my.toggle", whiteboard)) { + try (Feature feature = newFeatureWithSystemPropertyDefault("my.toggle", whiteboard)) { assertFalse(feature.isEnabled()); } finally { System.clearProperty(sysPropName);