This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 56aec1f802 feat: unify settings property source hierarchy and SVL
lookup
56aec1f802 is described below
commit 56aec1f80253cfd194d0b87267fcecaffe0e4cb8
Author: James Bognar <[email protected]>
AuthorDate: Thu May 14 12:16:50 2026 -0400
feat: unify settings property source hierarchy and SVL lookup
---
.../apache/juneau/commons/lang/StringFormat.java | 2 +-
.../juneau/commons/reflect/AnnotationProvider.java | 4 +-
.../juneau/commons/reflect/ParameterInfo.java | 3 +-
.../commons/settings/ArgsPropertySource.java | 77 +++++
.../settings/ArgsPropertySourceProvider.java | 33 ++
.../commons/settings/DotenvPropertySource.java | 94 ++++++
.../settings/DotenvPropertySourceProvider.java | 33 ++
.../commons/settings/FunctionalPropertySource.java | 44 +++
.../commons/settings/FunctionalPropertyStore.java | 86 +++++
.../juneau/commons/settings/FunctionalSource.java | 107 ------
.../juneau/commons/settings/FunctionalStore.java | 183 -----------
.../settings/ManifestFilePropertySource.java | 62 ++++
.../ManifestFilePropertySourceProvider.java | 33 ++
.../apache/juneau/commons/settings/MapStore.java | 38 +--
.../commons/settings/PropertyLookupResult.java | 92 ++++++
.../juneau/commons/settings/PropertySource.java | 45 +++
.../commons/settings/PropertySourceProvider.java | 42 +++
.../juneau/commons/settings/PropertyStore.java | 43 +++
.../juneau/commons/settings/SettingSource.java | 72 ----
.../juneau/commons/settings/SettingStore.java | 78 -----
.../apache/juneau/commons/settings/Settings.java | 106 +++---
.../commons/settings/SystemEnvPropertySource.java | 31 ++
.../settings/SystemEnvPropertySourceProvider.java | 33 ++
.../settings/SystemPropertyPropertySource.java | 31 ++
.../SystemPropertyPropertySourceProvider.java | 33 ++
.../org/apache/juneau/commons/svl/VarList.java | 2 +
.../apache/juneau/commons/svl/vars/ArgsVar.java | 41 +--
.../juneau/commons/svl/vars/EnvVariablesVar.java | 7 +-
.../juneau/commons/svl/vars/ManifestFileVar.java | 16 +-
.../juneau/commons/svl/vars/PropertyVar.java | 44 +++
.../commons/svl/vars/SystemPropertiesVar.java | 13 +-
.../org/apache/juneau/commons/utils/IoUtils.java | 4 +-
.../apache/juneau/commons/utils/SystemUtils.java | 3 +-
....juneau.commons.settings.PropertySourceProvider | 20 ++
.../main/java/org/apache/juneau/config/Config.java | 4 +-
.../apache/juneau/config/ConfigPropertySource.java | 50 +++
.../org/apache/juneau/config/mod/XorEncodeMod.java | 3 +-
.../apache/juneau/microservice/Microservice.java | 2 +-
.../microservice/jetty/JettyMicroservice.java | 4 +-
.../org/apache/juneau/rest/client/RestClient.java | 6 +
.../juneau/commons/reflect/ParameterInfo_Test.java | 20 +-
.../commons/settings/PropertySources_Test.java | 364 +++++++++++++++++++++
.../juneau/commons/settings/Settings_Test.java | 43 +--
.../juneau/commons/svl/vars/PropertyVars_Test.java | 145 ++++++++
.../juneau/config/ConfigPropertySource_Test.java | 72 ++++
45 files changed, 1683 insertions(+), 585 deletions(-)
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/StringFormat.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/StringFormat.java
index e2d2101a54..ac886bb1f2 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/StringFormat.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/StringFormat.java
@@ -436,7 +436,7 @@ public final class StringFormat {
abstract void append(StringBuilder sb, Object[] args, Locale
locale);
}
- private static final CacheMode CACHE_MODE =
CacheMode.parse(System.getProperty("juneau.StringFormat.caching", "FULL"));
+ private static final CacheMode CACHE_MODE =
env("juneau.StringFormat.caching", CacheMode.FULL);
private static final Cache<String,StringFormat> CACHE =
Cache.of(String.class,
StringFormat.class).maxSize(1000).cacheMode(CACHE_MODE).build();
private static final Cache2<Locale,String,MessageFormat>
MESSAGE_FORMAT_CACHE = Cache2.of(Locale.class, String.class,
MessageFormat.class).maxSize(100).threadLocal().cacheMode(CACHE_MODE)
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationProvider.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationProvider.java
index bdba29bcb6..ba652841c8 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationProvider.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationProvider.java
@@ -450,7 +450,7 @@ public class AnnotationProvider {
* <li><c>FULL</c> - Uses ConcurrentHashMap (best performance)
* </ul>
*/
- private static final CacheMode CACHING_MODE =
CacheMode.parse(System.getProperty("juneau.annotationProvider.caching",
"FULL"));
+ private static final CacheMode CACHING_MODE =
env("juneau.annotationProvider.caching", CacheMode.FULL);
/**
* Enable logging of cache statistics on JVM shutdown.
@@ -460,7 +460,7 @@ public class AnnotationProvider {
* <br>Valid values: <c>TRUE</c>, <c>FALSE</c> (case-insensitive)
* <br>Default: <c>FALSE</c>
*/
- private static final boolean LOG_ON_EXIT =
bool(System.getProperty("juneau.annotationProvider.caching.logOnExit"));
+ private static final boolean LOG_ON_EXIT =
env("juneau.annotationProvider.caching.logOnExit", false);
//-----------------------------------------------------------------------------------------------------------------
// Builder
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ParameterInfo.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ParameterInfo.java
index 67312ecfa5..284538b82d 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ParameterInfo.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ParameterInfo.java
@@ -31,6 +31,7 @@ import java.util.stream.*;
import org.apache.juneau.commons.function.*;
import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.commons.settings.Settings;
import org.apache.juneau.commons.utils.*;
/**
@@ -114,7 +115,7 @@ public class ParameterInfo extends ElementInfo implements
Annotatable {
* <p>
* The supplier can be reset for testing purposes using {@link
#resetDisableParamNameDetection()}.
*/
- static final Memoizer<Boolean> DISABLE_PARAM_NAME_DETECTION =
memoizer(() -> Boolean.getBoolean("juneau.disableParamNameDetection"));
+ static final Memoizer<Boolean> DISABLE_PARAM_NAME_DETECTION =
memoizer(() ->
Settings.get().get("juneau.disableParamNameDetection").asBoolean().orElse(false));
/**
* Creates a ParameterInfo wrapper for the specified parameter.
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ArgsPropertySource.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ArgsPropertySource.java
new file mode 100644
index 0000000000..6416f24708
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ArgsPropertySource.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.function.*;
+
+import org.apache.juneau.commons.runtime.*;
+
+/**
+ * Property source backed by {@link Args}.
+ */
+public class ArgsPropertySource implements PropertySource {
+
+ private final Supplier<Args> argsSupplier;
+
+ /**
+ * Constructor.
+ *
+ * @param argsSupplier Supplier for args.
+ */
+ public ArgsPropertySource(Supplier<Args> argsSupplier) {
+ this.argsSupplier = argsSupplier;
+ }
+
+ /**
+ * Creates a source using the default command-line discovery.
+ *
+ * @return A new source.
+ */
+ public static ArgsPropertySource createDefault() {
+ return new
ArgsPropertySource(ArgsPropertySource::createDefaultArgs);
+ }
+
+ public static Args createDefaultArgs() {
+ var s = System.getProperty("sun.java.command");
+ if (ne(s)) {
+ var i = s.indexOf(' ');
+ return new Args(i == -1 ? "" : s.substring(i + 1));
+ }
+ return new Args(System.getProperty("juneau.args", ""));
+ }
+
+ @Override
+ public PropertyLookupResult get(String name) {
+ var args = argsSupplier.get();
+ if (args == null)
+ return PropertyLookupResult.missing();
+ try {
+ var index = Integer.parseInt(name);
+ var v = args.get(index);
+ return v.isPresent() ? PropertyLookupResult.present(v)
: PropertyLookupResult.missing();
+ } catch (@SuppressWarnings("unused") NumberFormatException e) {
+ // Fall through.
+ }
+ var values = args.getAll(name);
+ if (! values.isEmpty())
+ return
PropertyLookupResult.present(opt(String.join(",", values)));
+ var v = args.get(name);
+ return v.isPresent() ? PropertyLookupResult.present(v) :
PropertyLookupResult.missing();
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ArgsPropertySourceProvider.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ArgsPropertySourceProvider.java
new file mode 100644
index 0000000000..ffbfd0dd80
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ArgsPropertySourceProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+/**
+ * Provider for {@link ArgsPropertySource}.
+ */
+public class ArgsPropertySourceProvider implements PropertySourceProvider {
+
+ @Override
+ public PropertySource create() {
+ return ArgsPropertySource.createDefault();
+ }
+
+ @Override
+ public int order() {
+ return 50;
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/DotenvPropertySource.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/DotenvPropertySource.java
new file mode 100644
index 0000000000..66578a9dc9
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/DotenvPropertySource.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.io.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+
+/**
+ * Property source backed by a dotenv file.
+ */
+public class DotenvPropertySource implements PropertySource {
+
+ private static final String DEFAULT_PATH = ".env";
+ private static final String DOTENV_PATH_PROP = "juneau.dotenv.path";
+ private static final String DOTENV_PATH_ENV = "JUNEAU_DOTENV_PATH";
+
+ private final AtomicReference<Map<String,String>> map = new
AtomicReference<>();
+ private final Path path;
+
+ /**
+ * Constructor with default path discovery.
+ */
+ public DotenvPropertySource() {
+ this(resolvePath());
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param path Dotenv path.
+ */
+ public DotenvPropertySource(Path path) {
+ this.path = path;
+ }
+
+ private static Path resolvePath() {
+ var configured = System.getProperty(DOTENV_PATH_PROP);
+ if (configured == null || configured.isEmpty())
+ configured = System.getenv(DOTENV_PATH_ENV);
+ if (configured == null || configured.isEmpty())
+ configured = DEFAULT_PATH;
+ return Paths.get(configured);
+ }
+
+ @Override
+ public PropertyLookupResult get(String name) {
+ var values = map.updateAndGet(existing -> existing != null ?
existing : load(path));
+ var value = values.get(name);
+ return value == null ? PropertyLookupResult.missing() :
PropertyLookupResult.present(opt(value));
+ }
+
+ private static Map<String,String> load(Path path) {
+ if (path == null || ! Files.exists(path))
+ return Collections.emptyMap();
+ var m = new LinkedHashMap<String,String>();
+ try (var r = Files.newBufferedReader(path)) {
+ String line;
+ while ((line = r.readLine()) != null) {
+ line = line.trim();
+ if (! (line.isEmpty() || line.startsWith("#")))
{
+ var i = line.indexOf('=');
+ if (i > 0) {
+ var key = line.substring(0,
i).trim();
+ var value = line.substring(i +
1).trim();
+ if ((value.startsWith("\"") &&
value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'")))
+ value =
value.substring(1, value.length() - 1);
+ m.put(key, value);
+ }
+ }
+ }
+ } catch (@SuppressWarnings("unused") IOException unused) {
+ return Collections.emptyMap();
+ }
+ return m;
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/DotenvPropertySourceProvider.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/DotenvPropertySourceProvider.java
new file mode 100644
index 0000000000..7375bdcbb1
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/DotenvPropertySourceProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+/**
+ * Provider for {@link DotenvPropertySource}.
+ */
+public class DotenvPropertySourceProvider implements PropertySourceProvider {
+
+ @Override
+ public PropertySource create() {
+ return new DotenvPropertySource();
+ }
+
+ @Override
+ public int order() {
+ return 20;
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalPropertySource.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalPropertySource.java
new file mode 100644
index 0000000000..0c135be478
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalPropertySource.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.function.*;
+
+/**
+ * A functional interface for creating read-only {@link PropertySource}
instances from a function.
+ */
+@FunctionalInterface
+public interface FunctionalPropertySource extends PropertySource {
+
+ @Override
+ PropertyLookupResult get(String name);
+
+ /**
+ * Creates a functional source from a function that returns a string.
+ *
+ * @param function The function to delegate property lookups to. Must
not be <c>null</c>.
+ * @return A new functional property source instance.
+ */
+ static FunctionalPropertySource of(UnaryOperator<String> function) {
+ return name -> {
+ var v = function.apply(name);
+ return v == null ? PropertyLookupResult.missing() :
PropertyLookupResult.present(opt(v));
+ };
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalPropertyStore.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalPropertyStore.java
new file mode 100644
index 0000000000..054f81b47d
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalPropertyStore.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.function.*;
+
+import org.apache.juneau.commons.function.*;
+
+/**
+ * A writable {@link PropertyStore} implementation created from functional
interfaces.
+ */
+public class FunctionalPropertyStore implements PropertyStore {
+
+ private static final String ARG_reader = "reader";
+ private static final String ARG_writer = "writer";
+ private static final String ARG_unsetter = "unsetter";
+ private static final String ARG_clearer = "clearer";
+
+ private final UnaryOperator<String> reader;
+ private final BiConsumer<String, String> writer;
+ private final Consumer<String> unsetter;
+ private final Snippet clearer;
+
+ public FunctionalPropertyStore(
+ UnaryOperator<String> reader,
+ BiConsumer<String, String> writer,
+ Consumer<String> unsetter,
+ Snippet clearer
+ ) {
+ assertArgNotNull(ARG_reader, reader);
+ assertArgNotNull(ARG_writer, writer);
+ assertArgNotNull(ARG_unsetter, unsetter);
+ assertArgNotNull(ARG_clearer, clearer);
+ this.reader = reader;
+ this.writer = writer;
+ this.unsetter = unsetter;
+ this.clearer = clearer;
+ }
+
+ @Override
+ public PropertyLookupResult get(String name) {
+ var v = reader.apply(name);
+ return v == null ? PropertyLookupResult.missing() :
PropertyLookupResult.present(opt(v));
+ }
+
+ @Override
+ public void set(String name, String value) {
+ writer.accept(name, value);
+ }
+
+ @Override
+ public void unset(String name) {
+ unsetter.accept(name);
+ }
+
+ @Override
+ public void clear() {
+ safe(clearer::run);
+ }
+
+ public static FunctionalPropertyStore of(
+ UnaryOperator<String> reader,
+ BiConsumer<String, String> writer,
+ Consumer<String> unsetter,
+ Snippet clearer
+ ) {
+ return new FunctionalPropertyStore(reader, writer, unsetter,
clearer);
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalSource.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalSource.java
deleted file mode 100644
index 90f26d1cd9..0000000000
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalSource.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.juneau.commons.settings;
-
-import static org.apache.juneau.commons.utils.Utils.*;
-
-import java.util.*;
-import java.util.function.*;
-
-/**
- * A functional interface for creating read-only {@link SettingSource}
instances from a function.
- *
- * <p>
- * This functional interface allows you to create setting sources directly
from lambda expressions or method references,
- * making it easy to wrap existing property sources (e.g., {@link
System#getProperty(String)},
- * {@link System#getenv(String)}) as {@link SettingSource} instances.
- *
- * <p>
- * Functional sources are read-only and do not implement {@link SettingStore}.
If you need a writable source,
- * use {@link MapStore} or {@link FunctionalStore} instead.
- *
- * <h5 class='section'>Return Value Semantics:</h5>
- * <ul class='spaced-list'>
- * <li>If the function returns <c>null</c>, this source returns
<c>null</c> (key doesn't exist).
- * <li>If the function returns a non-null value, this source returns
<c>Optional.of(value)</c>.
- * </ul>
- *
- * <p>
- * Note: This source cannot distinguish between a key that doesn't exist and a
key that exists with a null value,
- * since the function only returns a <c>String</c>. If you need to distinguish
these cases, use {@link MapStore} instead.
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * <jc>// Create a read-only source directly from a lambda (returns
Optional)</jc>
- * Settings.<jsf>get</jsf>().addSource(name ->
opt(System.getProperty(name)));
- *
- * <jc>// Using the static factory method (takes Function<String,
String>)</jc>
- *
Settings.<jsf>get</jsf>().addSource(FunctionalSource.<jsf>of</jsf>(System::getProperty));
- *
- * <jc>// Create a read-only source from System.getenv</jc>
- *
Settings.<jsf>get</jsf>().addSource(FunctionalSource.<jsf>of</jsf>(System::getenv));
- *
- * <jc>// Explicit creation for reuse</jc>
- * FunctionalSource <jv>sysProps</jv> =
FunctionalSource.<jsf>of</jsf>(System::getProperty);
- * Settings.<jsf>get</jsf>().addSource(<jv>sysProps</jv>);
- * </p>
- */
-@FunctionalInterface
-public interface FunctionalSource extends SettingSource {
-
- /**
- * Returns a setting by applying the function.
- *
- * <p>
- * If the function returns <c>null</c>, this method returns <c>null</c>
(indicating the key doesn't exist).
- * If the function returns a non-null value, this method returns
<c>Optional.of(value)</c>.
- *
- * @param name The property name.
- * @return The property value, or <c>null</c> if the function returns
<c>null</c>.
- */
- @Override
- Optional<String> get(String name);
-
- /**
- * Creates a functional source from a function that returns a string.
- *
- * <p>
- * This is a convenience factory method for creating functional sources
from functions that return
- * <c>String</c> values. The function's return value is converted to an
<c>Optional</c> as follows:
- * <ul>
- * <li>If the function returns <c>null</c>, the source returns
<c>null</c> (key doesn't exist).
- * <li>If the function returns a non-null value, the source
returns <c>Optional.of(value)</c>.
- * </ul>
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * <jc>// Create from a lambda</jc>
- * FunctionalSource <jv>source1</jv> =
FunctionalSource.<jsf>of</jsf>(name -> System.getProperty(name));
- *
- * <jc>// Create from a method reference</jc>
- * FunctionalSource <jv>source2</jv> =
FunctionalSource.<jsf>of</jsf>(System::getProperty);
- * </p>
- *
- * @param function The function to delegate property lookups to. Must
not be <c>null</c>.
- * @return A new functional source instance.
- */
- static FunctionalSource of(UnaryOperator<String> function) {
- return name -> {
- var v = function.apply(name);
- return v == null ? null : opt(v);
- };
- }
-}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalStore.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalStore.java
deleted file mode 100644
index 6ca511a3f9..0000000000
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalStore.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.juneau.commons.settings;
-
-import static org.apache.juneau.commons.utils.AssertionUtils.*;
-import static org.apache.juneau.commons.utils.Utils.*;
-
-import java.util.*;
-import java.util.function.*;
-
-import org.apache.juneau.commons.function.Snippet;
-
-/**
- * A writable {@link SettingStore} implementation created from functional
interfaces.
- *
- * <p>
- * This class allows you to create writable setting stores from lambda
expressions or method references,
- * making it easy to wrap existing property systems (e.g., custom
configuration systems) as
- * {@link SettingStore} instances.
- *
- * <h5 class='section'>Return Value Semantics:</h5>
- * <ul class='spaced-list'>
- * <li>If the reader function returns <c>null</c>, this store returns
<c>null</c> (key doesn't exist).
- * <li>If the reader function returns a non-null value, this store returns
<c>Optional.of(value)</c>.
- * </ul>
- *
- * <p>
- * Note: This store cannot distinguish between a key that doesn't exist and a
key that exists with a null value,
- * since the reader function only returns a <c>String</c>. If you need to
distinguish these cases, use {@link MapStore} instead.
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * <jc>// Create a writable functional store</jc>
- * FunctionalStore <jv>store</jv> = FunctionalStore.<jsf>of</jsf>(
- * System::getProperty, <jc>// reader</jc>
- * (k, v) -> System.setProperty(k, v), <jc>// writer</jc>
- * k -> System.clearProperty(k), <jc>// unset</jc>
- * () -> { <jc>// clear</jc>
- * <jc>// Clear all properties logic</jc>
- * }
- * );
- *
- * <jc>// Use it</jc>
- * <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
- * Optional<String> <jv>value</jv> =
<jv>store</jv>.get(<js>"my.property"</js>);
- * <jv>store</jv>.unset(<js>"my.property"</js>);
- * </p>
- */
-@SuppressWarnings({
- "java:S115" // Constants use UPPER_snakeCase convention
-})
-public class FunctionalStore implements SettingStore {
-
- // Argument name constants for assertArgNotNull
- private static final String ARG_reader = "reader";
- private static final String ARG_writer = "writer";
- private static final String ARG_unsetter = "unsetter";
- private static final String ARG_clearer = "clearer";
-
- private final UnaryOperator<String> reader;
- private final BiConsumer<String, String> writer;
- private final Consumer<String> unsetter;
- private final Snippet clearer;
-
- /**
- * Creates a new writable functional store.
- *
- * @param reader The function to read property values. Must not be
<c>null</c>.
- * @param writer The function to write property values. Must not be
<c>null</c>.
- * @param unsetter The function to remove property values. Must not be
<c>null</c>.
- * @param clearer The snippet to clear all property values. Must not be
<c>null</c>.
- */
- public FunctionalStore(
- UnaryOperator<String> reader,
- BiConsumer<String, String> writer,
- Consumer<String> unsetter,
- Snippet clearer
- ) {
- assertArgNotNull(ARG_reader, reader);
- assertArgNotNull(ARG_writer, writer);
- assertArgNotNull(ARG_unsetter, unsetter);
- assertArgNotNull(ARG_clearer, clearer);
- this.reader = reader;
- this.writer = writer;
- this.unsetter = unsetter;
- this.clearer = clearer;
- }
-
- /**
- * Returns a setting by applying the reader function.
- *
- * <p>
- * If the reader function returns <c>null</c>, this method returns
<c>null</c> (indicating the key doesn't exist).
- * If the reader function returns a non-null value, this method returns
<c>Optional.of(value)</c>.
- *
- * @param name The property name.
- * @return The property value, or <c>null</c> if the reader function
returns <c>null</c>.
- */
- @Override
- public Optional<String> get(String name) {
- var v = reader.apply(name);
- return v == null ? null : opt(v);
- }
-
- /**
- * Sets a setting by applying the writer function.
- *
- * @param name The property name.
- * @param value The property value, or <c>null</c> to set an empty
override.
- */
- @Override
- public void set(String name, String value) {
- writer.accept(name, value);
- }
-
- /**
- * Removes a setting by applying the unsetter function.
- *
- * @param name The property name to remove.
- */
- @Override
- public void unset(String name) {
- unsetter.accept(name);
- }
-
- /**
- * Clears all settings by invoking the clearer snippet.
- *
- * <p>
- * If the clearer snippet throws an exception, it will be wrapped in a
{@link RuntimeException}.
- */
- @Override
- public void clear() {
- safe(clearer::run);
- }
-
- /**
- * Creates a writable functional store from four functions.
- *
- * <p>
- * This is a convenience factory method for creating writable
functional stores.
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * <jc>// Create from lambdas</jc>
- * FunctionalStore <jv>store</jv> = FunctionalStore.<jsf>of</jsf>(
- * System::getProperty,
- * (k, v) -> System.setProperty(k, v),
- * k -> System.clearProperty(k),
- * () -> { <jc>// Clear all properties</jc> }
- * );
- * </p>
- *
- * @param reader The function to read property values. Must not be
<c>null</c>.
- * @param writer The function to write property values. Must not be
<c>null</c>.
- * @param unsetter The function to remove property values. Must not be
<c>null</c>.
- * @param clearer The snippet to clear all property values. Must not be
<c>null</c>.
- * @return A new writable functional store instance.
- */
- public static FunctionalStore of(
- UnaryOperator<String> reader,
- BiConsumer<String, String> writer,
- Consumer<String> unsetter,
- Snippet clearer
- ) {
- return new FunctionalStore(reader, writer, unsetter, clearer);
- }
-}
-
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ManifestFilePropertySource.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ManifestFilePropertySource.java
new file mode 100644
index 0000000000..efcc7f7239
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ManifestFilePropertySource.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import java.util.function.*;
+
+import org.apache.juneau.commons.runtime.*;
+
+/**
+ * Property source backed by {@link ManifestFile}.
+ */
+public class ManifestFilePropertySource implements PropertySource {
+
+ private final Supplier<ManifestFile> manifestSupplier;
+
+ /**
+ * Constructor.
+ *
+ * @param manifestSupplier The supplier for manifest file.
+ */
+ public ManifestFilePropertySource(Supplier<ManifestFile>
manifestSupplier) {
+ this.manifestSupplier = manifestSupplier;
+ }
+
+ /**
+ * Creates a source with default classloader scanning behavior.
+ *
+ * @return A new source.
+ */
+ public static ManifestFilePropertySource createDefault() {
+ return new ManifestFilePropertySource(() -> {
+ try {
+ return new
ManifestFile(ManifestFilePropertySource.class);
+ } catch (@SuppressWarnings("unused") Exception unused) {
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public PropertyLookupResult get(String name) {
+ var mf = manifestSupplier.get();
+ if (mf == null)
+ return PropertyLookupResult.missing();
+ var v = mf.get(name);
+ return v.isPresent() ? PropertyLookupResult.present(v) :
PropertyLookupResult.missing();
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ManifestFilePropertySourceProvider.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ManifestFilePropertySourceProvider.java
new file mode 100644
index 0000000000..ac456f7853
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ManifestFilePropertySourceProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+/**
+ * Provider for {@link ManifestFilePropertySource}.
+ */
+public class ManifestFilePropertySourceProvider implements
PropertySourceProvider {
+
+ @Override
+ public PropertySource create() {
+ return ManifestFilePropertySource.createDefault();
+ }
+
+ @Override
+ public int order() {
+ return 10;
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapStore.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapStore.java
index 2e826da92f..14c5129b77 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapStore.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapStore.java
@@ -23,7 +23,7 @@ import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
/**
- * A writable {@link SettingStore} implementation backed by a thread-safe map.
+ * A writable {@link PropertyStore} implementation backed by a thread-safe map.
*
* <p>
* This class provides a mutable store for settings that can be modified at
runtime. It's particularly useful
@@ -38,8 +38,9 @@ import java.util.concurrent.atomic.*;
* <h5 class='section'>Null Value Handling:</h5>
* <p>
* Setting a value to <c>null</c> stores <c>Optional.empty()</c> in the map,
which means {@link #get(String)}
- * will return <c>Optional.empty()</c> (not <c>null</c>). This allows you to
explicitly override system properties
- * with null values. Use {@link #unset(String)} if you want to remove a key
entirely (so {@link #get(String)} returns <c>null</c>).
+ * will return a {@link PropertyLookupResult#present(Optional)} result with an
empty optional.
+ * This allows you to explicitly override system properties with null values.
+ * Use {@link #unset(String)} if you want to remove a key entirely (so {@link
#get(String)} returns {@link PropertyLookupResult#missing()}).
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
@@ -53,14 +54,14 @@ import java.util.concurrent.atomic.*;
*
* <jc>// Override a system property with null</jc>
* <jv>store</jv>.set(<js>"system.property"</js>, <jk>null</jk>);
- * <jc>// get() will now return Optional.empty() for "system.property"</jc>
+ * <jc>// get() will now return present(Optional.empty()) for
"system.property"</jc>
*
* <jc>// Remove a property entirely</jc>
* <jv>store</jv>.unset(<js>"my.property"</js>);
- * <jc>// get() will now return null for "my.property"</jc>
+ * <jc>// get() will now return missing() for "my.property"</jc>
* </p>
*/
-public class MapStore implements SettingStore {
+public class MapStore implements PropertyStore {
private final AtomicReference<Map<String,Optional<String>>> map = new
AtomicReference<>();
@@ -68,19 +69,19 @@ public class MapStore implements SettingStore {
* Returns a setting from this store.
*
* <p>
- * Returns <c>null</c> if the key doesn't exist in the map, or the
stored value (which may be
- * <c>Optional.empty()</c> if the value was explicitly set to
<c>null</c>).
+ * Returns {@link PropertyLookupResult#missing()} if the key doesn't
exist in the map, or a present result
+ * (which may contain {@link Optional#empty()} if the value was
explicitly set to <c>null</c>).
*
* @param key The property name.
- * @return The property value, <c>null</c> if the key doesn't exist, or
<c>Optional.empty()</c> if the key
- * exists but has a null value.
+ * @return The property lookup result.
*/
@Override
- public Optional<String> get(String key) {
+ public PropertyLookupResult get(String key) {
var m = map.get();
if (m == null)
- return null;
- return m.get(key);
+ return PropertyLookupResult.missing();
+ var value = m.get(key);
+ return value == null ? PropertyLookupResult.missing() :
PropertyLookupResult.present(value);
}
/**
@@ -88,8 +89,8 @@ public class MapStore implements SettingStore {
*
* <p>
* The internal map is lazily initialized on the first call to this
method. Setting a value to <c>null</c>
- * stores <c>Optional.empty()</c> in the map, which means {@link
#get(String)} will return <c>Optional.empty()</c>
- * (not <c>null</c>). This allows you to explicitly override system
properties with null values.
+ * stores <c>Optional.empty()</c> in the map, which means {@link
#get(String)} will return a present result
+ * with an empty optional. This allows you to explicitly override
system properties with null values.
*
* @param key The property name.
* @param value The property value, or <c>null</c> to set an empty
override.
@@ -109,7 +110,7 @@ public class MapStore implements SettingStore {
*
* <p>
* After calling this method, all keys will be removed from the map,
and {@link #get(String)} will return
- * <c>null</c> for all keys.
+ * {@link PropertyLookupResult#missing()} for all keys.
*/
@Override
public void clear() {
@@ -122,9 +123,8 @@ public class MapStore implements SettingStore {
* Removes a setting from this store.
*
* <p>
- * After calling this method, {@link #get(String)} will return
<c>null</c> for the specified key,
- * indicating that the key doesn't exist in this store (as opposed to
returning <c>Optional.empty()</c>,
- * which would indicate the key exists but has a null value).
+ * After calling this method, {@link #get(String)} will return {@link
PropertyLookupResult#missing()} for
+ * the specified key, indicating that the key doesn't exist in this
store.
*
* @param name The property name to remove.
*/
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertyLookupResult.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertyLookupResult.java
new file mode 100644
index 0000000000..36062e87d6
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertyLookupResult.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.*;
+
+/**
+ * Tri-state result of a property lookup.
+ *
+ * <p>
+ * This type disambiguates source lookup semantics:
+ * <ul class='spaced-list'>
+ * <li>{@link #missing()} - The key does not exist in the source.
+ * <li>{@link #present(Optional)} with {@link Optional#empty()} - The key
exists and resolves to a null value.
+ * <li>{@link #present(Optional)} with a value - The key exists and
resolves to a non-null value.
+ * </ul>
+ */
+public final class PropertyLookupResult {
+
+ private static final PropertyLookupResult MISSING = new
PropertyLookupResult(false, opte());
+
+ private final boolean present;
+ private final Optional<String> value;
+
+ private PropertyLookupResult(boolean present, Optional<String> value) {
+ this.present = present;
+ this.value = value;
+ }
+
+ /**
+ * Returns a result indicating the key is absent in this source.
+ *
+ * @return A missing result.
+ */
+ public static PropertyLookupResult missing() {
+ return MISSING;
+ }
+
+ /**
+ * Returns a result indicating the key is present with the specified
value.
+ *
+ * @param value The value optional. Must not be <jk>null</jk>.
+ * @return A present result.
+ */
+ public static PropertyLookupResult present(Optional<String> value) {
+ return new PropertyLookupResult(true,
Objects.requireNonNull(value));
+ }
+
+ /**
+ * Returns a result indicating the key is present with the specified
value.
+ *
+ * @param value The value.
+ * @return A present result.
+ */
+ public static PropertyLookupResult present(String value) {
+ return present(opt(value));
+ }
+
+ /**
+ * Returns <jk>true</jk> if the key is present in this source.
+ *
+ * @return <jk>true</jk> if present.
+ */
+ public boolean isPresent() {
+ return present;
+ }
+
+ /**
+ * Returns the resolved value for present results.
+ *
+ * @return The value, never <jk>null</jk>.
+ */
+ public Optional<String> value() {
+ return value;
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySource.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySource.java
new file mode 100644
index 0000000000..708468c780
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySource.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import java.util.*;
+
+/**
+ * Interface for pluggable property sources used by {@link Settings}.
+ *
+ * <p>
+ * A property source provides a way to retrieve property values.
+ * Sources are checked in reverse order (last added is checked first) when
looking up properties.
+ *
+ * <p>
+ * For writable sources that support modifying property values, see {@link
PropertyStore}.
+ */
+public interface PropertySource {
+
+ /**
+ * Returns a property in this property source.
+ *
+ * @param name The property name.
+ * @return The property lookup result:
+ * <ul class='spaced-list'>
+ * <li>{@link PropertyLookupResult#missing()} if this
source does not define the key.
+ * <li>{@link PropertyLookupResult#present(Optional)} with
{@link Optional#empty()} if the key exists with a null value.
+ * <li>{@link PropertyLookupResult#present(Optional)} with
a value if the key exists with a non-null value.
+ * </ul>
+ */
+ PropertyLookupResult get(String name);
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySourceProvider.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySourceProvider.java
new file mode 100644
index 0000000000..0b5305aecc
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySourceProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+/**
+ * Provider SPI for augmenting {@link Settings} with additional property
sources.
+ */
+public interface PropertySourceProvider {
+
+ /**
+ * Creates a property source.
+ *
+ * @return The property source, or <jk>null</jk> to skip registration.
+ */
+ PropertySource create();
+
+ /**
+ * Sort key used by ServiceLoader wiring.
+ *
+ * <p>
+ * Lower values are added first.
+ *
+ * @return The order value.
+ */
+ default int order() {
+ return 0;
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertyStore.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertyStore.java
new file mode 100644
index 0000000000..255ce83a4e
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertyStore.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+/**
+ * A writable extension of {@link PropertySource} that supports modifying
property values.
+ */
+public interface PropertyStore extends PropertySource {
+
+ /**
+ * Sets a property in this store.
+ *
+ * @param name The property name.
+ * @param value The property value, or <c>null</c> to set an empty
override.
+ */
+ void set(String name, String value);
+
+ /**
+ * Removes a property from this store.
+ *
+ * @param name The property name to remove.
+ */
+ void unset(String name);
+
+ /**
+ * Clears all properties from this store.
+ */
+ void clear();
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingSource.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingSource.java
deleted file mode 100644
index 63e2fdf353..0000000000
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingSource.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.juneau.commons.settings;
-
-import java.util.*;
-
-/**
- * Interface for pluggable property sources used by {@link Settings}.
- *
- * <p>
- * A setting source provides a way to retrieve property values.
- * Sources are checked in reverse order (last added is checked first) when
looking up properties.
- *
- * <p>
- * For writable sources that support modifying property values, see {@link
SettingStore}.
- *
- * <h5 class='section'>Return Value Semantics:</h5>
- * <ul class='spaced-list'>
- * <li><c>null</c> - The setting does not exist in this source. The lookup
will continue to the next source.
- * <li><c>Optional.empty()</c> - The setting exists but has an explicitly
null value. This will be returned
- * immediately, overriding any values from lower-priority sources.
- * <li><c>Optional.of(value)</c> - The setting exists and has a non-null
value. This will be returned immediately.
- * </ul>
- *
- * <h5 class='section'>Examples:</h5>
- * <p class='bjava'>
- * <jc>// Create a read-only functional source directly from a lambda</jc>
- * FunctionalSource <jv>readOnly</jv> = name ->
opt(System.getProperty(name));
- *
- * <jc>// Or use the factory method</jc>
- * FunctionalSource <jv>readOnly2</jv> =
FunctionalSource.<jsf>of</jsf>(System::getProperty);
- *
- * <jc>// Stores can be used as sources (they extend SettingSource)</jc>
- * MapStore <jv>store</jv> = <jk>new</jk> MapStore();
- * <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
- * Settings.<jsf>get</jsf>().addSource(<jv>store</jv>); <jc>// Stores can
be added as sources</jc>
- * </p>
- */
-public interface SettingSource {
-
- /**
- * Returns a setting in this setting source.
- *
- * <p>
- * Return value semantics:
- * <ul>
- * <li><c>null</c> - The setting does not exist in this source.
The lookup will continue to the next source.
- * <li><c>Optional.empty()</c> - The setting exists but has an
explicitly null value. This will be returned
- * immediately, overriding any values from lower-priority
sources.
- * <li><c>Optional.of(value)</c> - The setting exists and has a
non-null value. This will be returned immediately.
- * </ul>
- *
- * @param name The property name.
- * @return The property value, <c>null</c> if the property doesn't
exist in this source, or <c>Optional.empty()</c>
- * if the property exists but has a null value.
- */
- Optional<String> get(String name);
-}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingStore.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingStore.java
deleted file mode 100644
index 5159b2be52..0000000000
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingStore.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.juneau.commons.settings;
-
-/**
- * A writable extension of {@link SettingSource} that supports modifying
property values.
- *
- * <p>
- * This interface extends {@link SettingSource} with methods for setting,
unsetting, and clearing properties.
- * All stores that implement this interface provide read/write access and can
be modified at runtime.
- *
- * <p>
- * <b>Sources vs Stores:</b>
- * <ul>
- * <li><b>Sources</b> ({@link SettingSource}) - Provide read-only access
to property values
- * <li><b>Stores</b> ({@link SettingStore}) - Provide read/write access to
property values
- * </ul>
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * <jc>// Create a writable store</jc>
- * MapStore <jv>store</jv> = <jk>new</jk> MapStore();
- * <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
- * <jv>store</jv>.unset(<js>"my.property"</js>);
- * <jv>store</jv>.clear();
- * </p>
- */
-public interface SettingStore extends SettingSource {
-
- /**
- * Sets a setting in this store.
- *
- * <p>
- * Setting a value to <c>null</c> means that {@link #get(String)} will
return <c>Optional.empty()</c> for that key,
- * effectively overriding any values from lower-priority sources. Use
{@link #unset(String)} if you want
- * {@link #get(String)} to return <c>null</c> (indicating the key
doesn't exist in this store).
- *
- * @param name The property name.
- * @param value The property value, or <c>null</c> to set an empty
override.
- */
- void set(String name, String value);
-
- /**
- * Removes a setting from this store.
- *
- * <p>
- * After calling this method, {@link #get(String)} will return
<c>null</c> for the specified key,
- * indicating that the key doesn't exist in this store (as opposed to
returning <c>Optional.empty()</c>,
- * which would indicate the key exists but has a null value).
- *
- * @param name The property name to remove.
- */
- void unset(String name);
-
- /**
- * Clears all settings from this store.
- *
- * <p>
- * After calling this method, all keys will be removed from this store,
and {@link #get(String)} will
- * return <c>null</c> for all keys.
- */
- void clear();
-}
-
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
index 71cb854a66..651fdc44a7 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
@@ -47,23 +47,23 @@ import org.apache.juneau.commons.reflect.*;
* <ol>
* <li>Per-thread store (if set via {@link #setLocal(String, String)})
* <li>Global store (if set via {@link #setGlobal(String, String)})
- * <li>Sources in reverse order (last source added via {@link
Builder#addSource(SettingSource)} is checked first)
+ * <li>Sources in reverse order (last source added via {@link
Builder#addSource(PropertySource)} is checked first)
* <li>System property source (default, always second-to-last)
* <li>System environment variable source (default, always last)
* </ol>
*
* <h5 class='section'>Sources vs Stores:</h5>
* <ul class='spaced-list'>
- * <li><b>Sources</b> ({@link SettingSource}) - Provide read-only access
to property values. Examples: {@link FunctionalSource}
- * <li><b>Stores</b> ({@link SettingStore}) - Provide read/write access to
property values. Examples: {@link MapStore}, {@link FunctionalStore}
- * <li>Stores can be used as sources (they extend {@link SettingSource}),
so you can add stores via {@link Builder#addSource(SettingSource)}
+ * <li><b>Sources</b> ({@link PropertySource}) - Provide read-only access
to property values. Examples: {@link FunctionalPropertySource}
+ * <li><b>Stores</b> ({@link PropertyStore}) - Provide read/write access
to property values. Examples: {@link MapStore}, {@link FunctionalPropertyStore}
+ * <li>Stores can be used as sources (they extend {@link PropertySource}),
so you can add stores via {@link Builder#addSource(PropertySource)}
* </ul>
*
* <h5 class='section'>Features:</h5>
* <ul class='spaced-list'>
* <li>System property access - read Java system properties with type
conversion via {@link StringSetting}
- * <li>Global overrides - override system properties globally for all
threads (stored in a {@link SettingStore})
- * <li>Per-thread overrides - override system properties for specific
threads (stored in a per-thread {@link SettingStore})
+ * <li>Global overrides - override system properties globally for all
threads (stored in a {@link PropertyStore})
+ * <li>Per-thread overrides - override system properties for specific
threads (stored in a per-thread {@link PropertyStore})
* <li>Custom sources - add arbitrary property sources (e.g., Spring
properties, environment variables, config files) via the {@link Builder}
* <li>Disable override support - system property to prevent new global
overrides from being set
* <li>Type-safe accessors - type conversion methods on {@link
StringSetting} for common types: Integer, Long, Boolean, Double, Float, File,
Path, URI, Charset
@@ -112,7 +112,7 @@ import org.apache.juneau.commons.reflect.*;
* <jv>springSource</jv>.set(<js>"spring.datasource.url"</js>,
<js>"jdbc:postgresql://localhost/db"</js>);
* Settings <jv>custom</jv> = Settings.<jsf>create</jsf>()
* .addSource(<jv>springSource</jv>)
- * .addSource(FunctionalSource.<jsf>of</jsf>(System::getProperty))
+ *
.addSource(FunctionalPropertySource.<jsf>of</jsf>(System::getProperty))
* .build();
* </p>
*
@@ -150,14 +150,14 @@ public class Settings {
/**
* System property source that delegates to {@link
System#getProperty(String)}.
*/
- public static final SettingSource SYSTEM_PROPERTY_SOURCE =
FunctionalSource.of(System::getProperty);
+ public static final PropertySource SYSTEM_PROPERTY_SOURCE =
FunctionalPropertySource.of(System::getProperty);
private static final Set<String> FROM_STRING_METHOD_NAMES = new
LinkedHashSet<>(Arrays.asList("fromString", "parse", "forName", "valueOf"));
/**
* System environment variable source that delegates to {@link
System#getenv(String)}.
*/
- public static final SettingSource SYSTEM_ENV_SOURCE =
FunctionalSource.of(System::getenv);
+ public static final PropertySource SYSTEM_ENV_SOURCE =
FunctionalPropertySource.of(System::getenv);
private static final String DISABLE_GLOBAL_PROP =
"juneau.settings.disableGlobal";
private static final String MSG_globalDisabled = "Global settings not
enabled";
@@ -167,16 +167,13 @@ public class Settings {
* Returns properties for this Settings object itself.
* Note that these are initialized at startup and not changeable
through System.setProperty().
*/
- @SuppressWarnings({
- "java:S2789" // null check on Optional is intentional -
SettingSource.get() returns null if key doesn't exist, Optional.empty() if key
exists with null value
- })
private static final Optional<String> initProperty(String property) {
var v = SYSTEM_PROPERTY_SOURCE.get(property);
- if (v != null)
- return v; // Not testable
+ if (v.isPresent())
+ return v.value(); // Not testable
v = SYSTEM_ENV_SOURCE.get(property.replace('.',
'_').toUpperCase());
- if (v != null)
- return v; // Not testable
+ if (v.isPresent())
+ return v.value(); // Not testable
return opte();
}
@@ -194,7 +191,7 @@ public class Settings {
* Settings <jv>custom</jv> = Settings.<jsf>create</jsf>()
* .globalStore(() -> <jk>new</jk> MapStore())
* .localStore(() -> <jk>new</jk> MapStore())
- *
.addSource(FunctionalSource.<jsf>of</jsf>(System::getProperty))
+ *
.addSource(FunctionalPropertySource.<jsf>of</jsf>(System::getProperty))
* .build();
* </p>
*
@@ -208,9 +205,9 @@ public class Settings {
* Builder for creating Settings instances.
*/
public static class Builder {
- private Supplier<SettingStore> globalStoreSupplier =
MapStore::new;
- private Supplier<SettingStore> localStoreSupplier =
MapStore::new;
- private final List<SettingSource> sources = new ArrayList<>();
+ private Supplier<PropertyStore> globalStoreSupplier =
MapStore::new;
+ private Supplier<PropertyStore> localStoreSupplier =
MapStore::new;
+ private final List<PropertySource> sources = new ArrayList<>();
private final Map<Class<?>,Function<String,?>>
customTypeFunctions = new IdentityHashMap<>();
/**
@@ -219,7 +216,7 @@ public class Settings {
* @param supplier The supplier for the global store. Must not
be <c>null</c>. Can supply null to disable global store.
* @return This builder for method chaining.
*/
- public Builder globalStore(NullableSupplier<SettingStore>
supplier) {
+ public Builder globalStore(NullableSupplier<PropertyStore>
supplier) {
this.globalStoreSupplier =
assertArgNotNull(ARG_supplier, supplier);
return this;
}
@@ -230,7 +227,7 @@ public class Settings {
* @param supplier The supplier for the local store. Must not
be <c>null</c>.
* @return This builder for method chaining.
*/
- public Builder localStore(NullableSupplier<SettingStore>
supplier) {
+ public Builder localStore(NullableSupplier<PropertyStore>
supplier) {
this.localStoreSupplier =
assertArgNotNull(ARG_supplier, supplier);
return this;
}
@@ -242,7 +239,7 @@ public class Settings {
* @return This builder for method chaining.
*/
@SafeVarargs
- public final Builder setSources(SettingSource...sources) {
+ public final Builder setSources(PropertySource...sources) {
assertArgNoNulls(ARG_sources, sources);
this.sources.clear();
for (var source : sources) {
@@ -257,7 +254,7 @@ public class Settings {
* @param source The source to add. Must not be <c>null</c>.
* @return This builder for method chaining.
*/
- public Builder addSource(SettingSource source) {
+ public Builder addSource(PropertySource source) {
assertArgNotNull(ARG_source, source);
this.sources.add(source);
return this;
@@ -269,8 +266,27 @@ public class Settings {
* @param source The functional source to add. Must not be
<c>null</c>.
* @return This builder for method chaining.
*/
- public Builder addSource(FunctionalSource source) {
- return addSource((SettingSource)source);
+ public Builder addSource(FunctionalPropertySource source) {
+ return addSource((PropertySource)source);
+ }
+
+ /**
+ * Discovers and registers {@link PropertySourceProvider}
instances via {@link ServiceLoader}.
+ *
+ * <p>
+ * Providers are sorted by {@link
PropertySourceProvider#order()} ascending.
+ *
+ * @return This builder for method chaining.
+ */
+ public Builder useServiceLoader() {
+ ServiceLoader.load(PropertySourceProvider.class)
+ .stream()
+ .map(ServiceLoader.Provider::get)
+
.sorted(Comparator.comparingInt(PropertySourceProvider::order))
+ .map(PropertySourceProvider::create)
+ .filter(Objects::nonNull)
+ .forEach(this::addSource);
+ return this;
}
/**
@@ -319,6 +335,7 @@ public class Settings {
private static final Settings INSTANCE = new Builder()
.globalStore(initProperty(DISABLE_GLOBAL_PROP).map(Boolean::valueOf).orElse(false)
? () -> null : MapStore::new)
.setSources(SYSTEM_ENV_SOURCE, SYSTEM_PROPERTY_SOURCE)
+ .useServiceLoader()
.build();
/**
@@ -330,12 +347,12 @@ public class Settings {
return INSTANCE;
}
- private final Memoizer<SettingStore> globalStore;
+ private final Memoizer<PropertyStore> globalStore;
@SuppressWarnings({
"java:S5164" // Cleanup method provided: cleanup()
})
- private final ThreadLocal<SettingStore> localStore;
- private final List<SettingSource> sources;
+ private final ThreadLocal<PropertyStore> localStore;
+ private final List<PropertySource> sources;
private final Map<Class<?>,Function<String,?>> toTypeFunctions;
/**
@@ -348,6 +365,18 @@ public class Settings {
this.toTypeFunctions = new
ConcurrentHashMap<>(builder.customTypeFunctions);
}
+ /**
+ * Adds a property source after this settings instance has been built.
+ *
+ * @param source The property source to add. Must not be <jk>null</jk>.
+ * @return This object for method chaining.
+ */
+ public Settings addSource(PropertySource source) {
+ assertArgNotNull(ARG_source, source);
+ sources.add(source);
+ return this;
+ }
+
/**
* Returns a {@link StringSetting} for the specified system property.
*
@@ -361,36 +390,33 @@ public class Settings {
* <ol>
* <li>Per-thread override (if set via {@link #setLocal(String,
String)})
* <li>Global override (if set via {@link #setGlobal(String,
String)})
- * <li>Sources in reverse order (last source added via {@link
Builder#addSource(SettingSource)} is checked first)
+ * <li>Sources in reverse order (last source added via {@link
Builder#addSource(PropertySource)} is checked first)
* <li>System property source (default, always second-to-last)
* <li>System environment variable source (default, always last)
* </ol>
*
* @param name The property name. Must not be <jk>null</jk>.
- * @return A {@link StringSetting} that provides the property value, or
<jk>null</jk> if not found.
+ * @return A {@link StringSetting} that provides the resolved property
value.
*/
- @SuppressWarnings({
- "java:S2789" // null check on Optional is intentional -
SettingStore.get()/SettingSource.get() return null if key doesn't exist,
Optional.empty() if key exists with null value
- })
public StringSetting get(String name) {
assertArgNotNull(ARG_name, name);
return new StringSetting(this, () -> {
// 1. Check thread-local override
var v = localStore.get().get(name);
- if (v != null)
- return v.orElse(null); // v is Optional.empty()
if key exists with null value, or Optional.of(value) if present
+ if (v.isPresent())
+ return v.value().orElse(null); // Present
result: Optional.empty() means explicit null override.
// 2. Check global override
v = globalStore.get().get(name);
- if (v != null)
- return v.orElse(null); // v is Optional.empty()
if key exists with null value, or Optional.of(value) if present
+ if (v.isPresent())
+ return v.value().orElse(null); // Present
result: Optional.empty() means explicit null override.
// 3. Check sources in reverse order (last added first)
for (int i = sources.size() - 1; i >= 0; i--) {
var source = sources.get(i);
var result = source.get(name);
- if (result != null)
- return result.orElse(null);
+ if (result.isPresent())
+ return result.value().orElse(null);
}
return null;
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemEnvPropertySource.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemEnvPropertySource.java
new file mode 100644
index 0000000000..bcc1aa59dc
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemEnvPropertySource.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+/**
+ * Property source backed by {@link System#getenv(String)}.
+ */
+public class SystemEnvPropertySource implements PropertySource {
+
+ @Override
+ public PropertyLookupResult get(String name) {
+ var v = System.getenv(name);
+ return v == null ? PropertyLookupResult.missing() :
PropertyLookupResult.present(opt(v));
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemEnvPropertySourceProvider.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemEnvPropertySourceProvider.java
new file mode 100644
index 0000000000..bb14ded7d7
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemEnvPropertySourceProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+/**
+ * Provider for {@link SystemEnvPropertySource}.
+ */
+public class SystemEnvPropertySourceProvider implements PropertySourceProvider
{
+
+ @Override
+ public PropertySource create() {
+ return new SystemEnvPropertySource();
+ }
+
+ @Override
+ public int order() {
+ return 30;
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemPropertyPropertySource.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemPropertyPropertySource.java
new file mode 100644
index 0000000000..28f0bff73b
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemPropertyPropertySource.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+/**
+ * Property source backed by {@link System#getProperty(String)}.
+ */
+public class SystemPropertyPropertySource implements PropertySource {
+
+ @Override
+ public PropertyLookupResult get(String name) {
+ var v = System.getProperty(name);
+ return v == null ? PropertyLookupResult.missing() :
PropertyLookupResult.present(opt(v));
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemPropertyPropertySourceProvider.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemPropertyPropertySourceProvider.java
new file mode 100644
index 0000000000..80caf735d9
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemPropertyPropertySourceProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+/**
+ * Provider for {@link SystemPropertyPropertySource}.
+ */
+public class SystemPropertyPropertySourceProvider implements
PropertySourceProvider {
+
+ @Override
+ public PropertySource create() {
+ return new SystemPropertyPropertySource();
+ }
+
+ @Override
+ public int order() {
+ return 40;
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarList.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarList.java
index 0ed70da91a..78d2935c63 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarList.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarList.java
@@ -85,6 +85,7 @@ public class VarList extends ArrayList<Object> {
* <p>
* The default variables are:
* <ul>
+ * <li>{@link PropertyVar}
* <li>{@link SystemPropertiesVar}
* <li>{@link EnvVariablesVar}
* <li>{@link ArgsVar}
@@ -107,6 +108,7 @@ public class VarList extends ArrayList<Object> {
public VarList addDefault() {
// @formatter:off
return append(
+ PropertyVar.class,
SystemPropertiesVar.class,
EnvVariablesVar.class,
ManifestFileVar.class,
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ArgsVar.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ArgsVar.java
index 45887033a0..1d8dbf38a2 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ArgsVar.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ArgsVar.java
@@ -16,12 +16,11 @@
*/
package org.apache.juneau.commons.svl.vars;
-import static org.apache.juneau.commons.utils.Utils.*;
-
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.*;
import java.util.function.*;
import org.apache.juneau.commons.runtime.*;
+import org.apache.juneau.commons.settings.*;
import org.apache.juneau.commons.svl.*;
/**
@@ -69,7 +68,7 @@ public class ArgsVar extends DefaultingVar {
/** The name of this variable. */
public static final String NAME = "A";
- private static final AtomicReference<Args> staticArgs = new
AtomicReference<>();
+ private static final AtomicReference<Supplier<Args>>
STATIC_ARGS_SUPPLIER = new
AtomicReference<>(ArgsPropertySource::createDefaultArgs);
/**
* Initialize the args for this variable.
@@ -81,7 +80,7 @@ public class ArgsVar extends DefaultingVar {
* @param args The parsed command-line arguments.
*/
public static void init(Args args) {
- staticArgs.set(args);
+ STATIC_ARGS_SUPPLIER.set(() -> args);
}
/**
@@ -99,46 +98,24 @@ public class ArgsVar extends DefaultingVar {
return new ArgsVar(supplier);
}
- private final Supplier<Args> argsSupplier;
+ private final ArgsPropertySource source;
/**
* Constructor.
*/
public ArgsVar() {
super(NAME);
- var captured = computeDefault();
- this.argsSupplier = () -> captured;
+ this.source = new ArgsPropertySource(() ->
STATIC_ARGS_SUPPLIER.get().get());
}
private ArgsVar(Supplier<Args> supplier) {
super(NAME);
- this.argsSupplier = supplier;
- }
-
- private static Args computeDefault() {
- var sa = staticArgs.get();
- if (nn(sa))
- return sa;
- var s = System.getProperty("sun.java.command");
- if (ne(s)) {
- var i = s.indexOf(' ');
- return new Args(i == -1 ? "" : s.substring(i + 1));
- }
- return new Args(System.getProperty("juneau.args", ""));
+ this.source = new ArgsPropertySource(supplier);
}
@Override /* Overridden from Var */
public String resolve(VarResolverSession session, String key) {
- var args = argsSupplier.get();
- if (args == null)
- return null;
- try {
- var idx = Integer.parseInt(key);
- return args.get(idx).orElse(null);
- } catch (@SuppressWarnings("unused") NumberFormatException e) {
- // not a positional index; fall through to named-option
lookup
- }
- var values = args.getAll(key);
- return values.isEmpty() ? null : String.join(",", values);
+ var result = source.get(key);
+ return result.isPresent() ? result.value().orElse(null) : null;
}
}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/EnvVariablesVar.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/EnvVariablesVar.java
index 74e3895ae1..05b75f8eb1 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/EnvVariablesVar.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/EnvVariablesVar.java
@@ -17,6 +17,7 @@
package org.apache.juneau.commons.svl.vars;
import org.apache.juneau.commons.svl.*;
+import org.apache.juneau.commons.settings.*;
/**
* Environment variable variable resolver.
@@ -42,6 +43,8 @@ import org.apache.juneau.commons.svl.*;
* </ul>
*/
public class EnvVariablesVar extends DefaultingVar {
+ private final SystemEnvPropertySource source = new
SystemEnvPropertySource();
+
/** The name of this variable. */
public static final String NAME = "E";
@@ -55,7 +58,7 @@ public class EnvVariablesVar extends DefaultingVar {
@Override /* Overridden from Var */
public String resolve(VarResolverSession session, String varVal) {
- // Note that lookup is case-insensitive on windows.
- return System.getenv(varVal);
+ var v = source.get(varVal);
+ return v.isPresent() ? v.value().orElse(null) : null;
}
}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ManifestFileVar.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ManifestFileVar.java
index 8f70c9e83a..bc6a3fdd90 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ManifestFileVar.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ManifestFileVar.java
@@ -16,10 +16,10 @@
*/
package org.apache.juneau.commons.svl.vars;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.function.*;
import org.apache.juneau.commons.runtime.*;
+import org.apache.juneau.commons.settings.*;
import org.apache.juneau.commons.svl.*;
/**
@@ -62,7 +62,7 @@ public class ManifestFileVar extends DefaultingVar {
/** The name of this variable. */
public static final String NAME = "MF";
- private static final AtomicReference<ManifestFile> manifestFile = new
AtomicReference<>();
+ private static volatile Supplier<ManifestFile> manifestSupplier = () ->
null;
/**
* Initialize the manifest file for this variable.
@@ -74,7 +74,7 @@ public class ManifestFileVar extends DefaultingVar {
* @param manifestFile The parsed manifest file.
*/
public static void init(ManifestFile manifestFile) {
- ManifestFileVar.manifestFile.set(manifestFile);
+ ManifestFileVar.manifestSupplier = () -> manifestFile;
}
/**
@@ -91,24 +91,24 @@ public class ManifestFileVar extends DefaultingVar {
return new ManifestFileVar(supplier);
}
- private final Supplier<ManifestFile> manifestSupplier;
+ private final ManifestFilePropertySource source;
/**
* Constructor.
*/
public ManifestFileVar() {
super(NAME);
- this.manifestSupplier = manifestFile::get;
+ this.source = new ManifestFilePropertySource(() ->
manifestSupplier.get());
}
private ManifestFileVar(Supplier<ManifestFile> supplier) {
super(NAME);
- this.manifestSupplier = supplier;
+ this.source = new ManifestFilePropertySource(supplier);
}
@Override /* Overridden from Var */
public String resolve(VarResolverSession session, String key) {
- var mf = manifestSupplier.get();
- return mf == null ? "" : mf.get(key).orElse(null);
+ var v = source.get(key);
+ return v.isPresent() ? v.value().orElse(null) : "";
}
}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/PropertyVar.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/PropertyVar.java
new file mode 100644
index 0000000000..9aeba335b3
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/PropertyVar.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.svl.vars;
+
+import org.apache.juneau.commons.settings.*;
+import org.apache.juneau.commons.svl.*;
+
+/**
+ * Unified property variable resolver.
+ *
+ * <p>
+ * The format for this var is <js>"$P{propertyName[,defaultValue]}"</js>.
+ */
+public class PropertyVar extends DefaultingVar {
+
+ /** The name of this variable. */
+ public static final String NAME = "P";
+
+ /**
+ * Constructor.
+ */
+ public PropertyVar() {
+ super(NAME);
+ }
+
+ @Override /* Overridden from Var */
+ public String resolve(VarResolverSession session, String key) {
+ return Settings.get().get(key).orElse(null);
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/SystemPropertiesVar.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/SystemPropertiesVar.java
index 8e436c4709..0a315ccbf1 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/SystemPropertiesVar.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/SystemPropertiesVar.java
@@ -17,6 +17,7 @@
package org.apache.juneau.commons.svl.vars;
import org.apache.juneau.commons.svl.*;
+import org.apache.juneau.commons.settings.*;
/**
* System property variable resolver.
@@ -41,7 +42,9 @@ import org.apache.juneau.commons.svl.*;
* <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SimpleVariableLanguageBasics">Simple
Variable Language Basics</a>
* </ul>
*/
-public class SystemPropertiesVar extends MapVar {
+public class SystemPropertiesVar extends DefaultingVar {
+ private final SystemPropertyPropertySource source = new
SystemPropertyPropertySource();
+
/** The name of this variable. */
public static final String NAME = "S";
@@ -50,6 +53,12 @@ public class SystemPropertiesVar extends MapVar {
* Constructor.
*/
public SystemPropertiesVar() {
- super(NAME, System.getProperties());
+ super(NAME);
+ }
+
+ @Override /* Overridden from Var */
+ public String resolve(VarResolverSession session, String key) {
+ var v = source.get(key);
+ return v.isPresent() ? v.value().orElse(null) : null;
}
}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
index cbb45173dc..ed2b148b14 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
@@ -51,11 +51,11 @@ public class IoUtils {
@SuppressWarnings({
"java:S5164" // Cleanup method provided: cleanupThreadLocals()
})
- private static final ThreadLocal<byte[]> BYTE_BUFFER_CACHE =
(Boolean.getBoolean("juneau.disableIoBufferReuse") ? null : new
ThreadLocal<>());
+ private static final ThreadLocal<byte[]> BYTE_BUFFER_CACHE =
(env("juneau.disableIoBufferReuse", false) ? null : new ThreadLocal<>());
@SuppressWarnings({
"java:S5164" // Cleanup method provided: cleanupThreadLocals()
})
- private static final ThreadLocal<char[]> CHAR_BUFFER_CACHE =
(Boolean.getBoolean("juneau.disableIoBufferReuse") ? null : new
ThreadLocal<>());
+ private static final ThreadLocal<char[]> CHAR_BUFFER_CACHE =
(env("juneau.disableIoBufferReuse", false) ? null : new ThreadLocal<>());
static final AtomicInteger BYTE_BUFFER_CACHE_HITS = new AtomicInteger();
static final AtomicInteger BYTE_BUFFER_CACHE_MISSES = new
AtomicInteger();
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/SystemUtils.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/SystemUtils.java
index d2923fa2a9..e3f37e60a5 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/SystemUtils.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/SystemUtils.java
@@ -21,6 +21,7 @@ import java.util.concurrent.*;
import java.util.function.*;
import org.apache.juneau.commons.logging.Logger;
+import org.apache.juneau.commons.settings.*;
/**
* System utilities.
@@ -40,7 +41,7 @@ public class SystemUtils {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
- if (!
Boolean.getBoolean("juneau.shutdown.quiet")) // HTT - shutdown hook; true
branch tested, false requires JVM shutdown with system property set
+ if (!
Settings.get().get("juneau.shutdown.quiet").asBoolean().orElse(false)) // HTT -
shutdown hook; true branch tested, false requires JVM shutdown with system
property set
SHUTDOWN_MESSAGES.forEach(x ->
LOG.info(x.get()));
}
});
diff --git
a/juneau-core/juneau-commons/src/main/resources/META-INF/services/org.apache.juneau.commons.settings.PropertySourceProvider
b/juneau-core/juneau-commons/src/main/resources/META-INF/services/org.apache.juneau.commons.settings.PropertySourceProvider
new file mode 100644
index 0000000000..f503f5f57e
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/resources/META-INF/services/org.apache.juneau.commons.settings.PropertySourceProvider
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+org.apache.juneau.commons.settings.ManifestFilePropertySourceProvider
+org.apache.juneau.commons.settings.DotenvPropertySourceProvider
+org.apache.juneau.commons.settings.SystemEnvPropertySourceProvider
+org.apache.juneau.commons.settings.SystemPropertyPropertySourceProvider
+org.apache.juneau.commons.settings.ArgsPropertySourceProvider
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
index 0ae0394a20..e96232c70f 100644
---
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
@@ -437,7 +437,7 @@ public class Config extends Context implements
ConfigEventListener {
}
// Use set(T)/reset() for testing.
- static final Memoizer<Boolean> DISABLE_AUTO_SYSTEM_PROPS = memoizer(()
-> Boolean.getBoolean("juneau.disableAutoSystemProps"));
+ static final Memoizer<Boolean> DISABLE_AUTO_SYSTEM_PROPS = memoizer(()
-> env("juneau.disableAutoSystemProps", false));
// Use set(T)/reset() for testing.
static final Memoizer<Config> SYSTEM_DEFAULT =
memoizer(Config::findSystemDefault);
@@ -487,7 +487,7 @@ public class Config extends Context implements
ConfigEventListener {
public static synchronized List<String>
getCandidateSystemDefaultConfigNames() {
var l = listOf(String.class);
- var s = System.getProperty("juneau.configFile");
+ var s = env("juneau.configFile").orElse(null);
if (nn(s)) {
l.add(s);
return l;
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigPropertySource.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigPropertySource.java
new file mode 100644
index 0000000000..cbc3a032c6
--- /dev/null
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigPropertySource.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.config;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import org.apache.juneau.commons.settings.*;
+
+/**
+ * {@link PropertySource} adapter for {@link Config}.
+ */
+public class ConfigPropertySource implements PropertySource {
+
+ private final Config config;
+
+ /**
+ * Constructor.
+ *
+ * @param config The wrapped config.
+ */
+ public ConfigPropertySource(Config config) {
+ this.config = config;
+ }
+
+ @Override
+ public PropertyLookupResult get(String name) {
+ if (config == null)
+ return PropertyLookupResult.missing();
+ try {
+ var value = config.getString(name);
+ return value == null ? PropertyLookupResult.missing() :
PropertyLookupResult.present(opt(value));
+ } catch (@SuppressWarnings("unused") Exception unused) {
+ return PropertyLookupResult.missing();
+ }
+ }
+}
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/mod/XorEncodeMod.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/mod/XorEncodeMod.java
index c66c15751f..e877445035 100644
---
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/mod/XorEncodeMod.java
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/mod/XorEncodeMod.java
@@ -18,6 +18,7 @@ package org.apache.juneau.config.mod;
import static org.apache.juneau.commons.utils.IoUtils.*;
import static org.apache.juneau.commons.utils.StringUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
/**
* Simply XOR+Base64 encoder for obscuring passwords and other sensitive data
in INI config files.
@@ -34,7 +35,7 @@ public class XorEncodeMod extends Mod {
/** Reusable XOR-ConfigEncoder instance. */
public static final XorEncodeMod INSTANCE = new XorEncodeMod();
- private static final String KEY =
System.getProperty("org.apache.juneau.config.XorEncoder.key",
"nuy7og796Vh6G9O6bG230SHK0cc8QYkH"); // The super-duper-secret key
+ private static final String KEY =
env("org.apache.juneau.config.XorEncoder.key",
"nuy7og796Vh6G9O6bG230SHK0cc8QYkH"); // The super-duper-secret key
/**
* Constructor.
diff --git
a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
index af6c1db1f8..787af6bbe3 100755
---
a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
+++
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
@@ -123,7 +123,7 @@ public class Microservice implements ConfigEventListener {
Scanner consoleReader;
PrintWriter consoleWriter;
MicroserviceListener listener;
- File workingDir = System.getProperty("juneau.workingDir") ==
null ? null : new File(System.getProperty("juneau.workingDir"));
+ File workingDir =
env("juneau.workingDir").map(File::new).orElse(null);
/**
* Constructor.
diff --git
a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroservice.java
b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroservice.java
index d4d5d005ce..92675f4862 100644
---
a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroservice.java
+++
b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroservice.java
@@ -566,7 +566,7 @@ public class JettyMicroservice extends Microservice {
var ports = firstNonNull(builder.ports,
cf.get("Jetty/port").as(int[].class).orElseGet(() ->
mf.get("Jetty-Port").map(JettyMicroservice::parseIntArray).orElseGet(() ->
ints(8000))));
var availablePort = findOpenPort(ports);
- if (System.getProperty("availablePort") == null)
+ if (env("availablePort").isEmpty())
System.setProperty("availablePort",
String.valueOf(availablePort));
var jettyXml = builder.jettyXml;
@@ -624,7 +624,7 @@ public class JettyMicroservice extends Microservice {
builder.servletAttributes.forEach(this::addServletAttribute);
- if (System.getProperty("juneau.serverPort") == null)
+ if (env("juneau.serverPort").isEmpty())
System.setProperty("juneau.serverPort",
String.valueOf(availablePort));
return server.get();
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index ee0c09b920..8519ff21e1 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -7044,6 +7044,9 @@ public class RestClient extends MarshallingContextable
implements HttpClient, Cl
* @deprecated Use {@link HttpClientBuilder}.
*/
@Deprecated(since = "10.0", forRemoval = true)
+ @SuppressWarnings({
+ "java:S1133" // Deprecated override required by HttpClient
interface
+ })
@Override /* Overridden from HttpClient */
public ClientConnectionManager getConnectionManager() { return
httpClient.getConnectionManager(); }
@@ -7063,6 +7066,9 @@ public class RestClient extends MarshallingContextable
implements HttpClient, Cl
* @deprecated Use {@link RequestConfig}.
*/
@Deprecated(since = "10.0", forRemoval = true)
+ @SuppressWarnings({
+ "java:S1133" // Deprecated override required by HttpClient
interface
+ })
@Override /* Overridden from HttpClient */
public HttpParams getParams() { return httpClient.getParams(); }
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfo_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfo_Test.java
index bd273f7365..1f3ea9c055 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfo_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfo_Test.java
@@ -30,6 +30,7 @@ import java.util.stream.*;
import org.apache.juneau.*;
import org.apache.juneau.commons.bean.Name;
+import org.apache.juneau.commons.settings.*;
import org.junit.jupiter.api.*;
@SuppressWarnings({
@@ -46,11 +47,11 @@ class ParameterInfo_Test extends TestBase {
@BeforeAll
static void beforeAll() {
- // Save original system property value
- originalDisableParamNameDetection =
System.getProperty("juneau.disableParamNameDetection");
+ // Save original effective value.
+ originalDisableParamNameDetection =
Settings.get().get("juneau.disableParamNameDetection").orElse(null);
// Set to true to ensure consistent behavior regardless of JVM
compiler settings
- System.setProperty("juneau.disableParamNameDetection", "true");
+ Settings.get().setLocal("juneau.disableParamNameDetection",
"true");
ParameterInfo.reset();
}
@@ -58,10 +59,11 @@ class ParameterInfo_Test extends TestBase {
static void afterAll() {
// Restore original system property value
if (originalDisableParamNameDetection == null)
-
System.clearProperty("juneau.disableParamNameDetection");
+
Settings.get().unsetLocal("juneau.disableParamNameDetection");
else
- System.setProperty("juneau.disableParamNameDetection",
originalDisableParamNameDetection);
+
Settings.get().setLocal("juneau.disableParamNameDetection",
originalDisableParamNameDetection);
ParameterInfo.reset();
+ Settings.get().clearLocal();
}
@Documented
@@ -649,9 +651,9 @@ class ParameterInfo_Test extends TestBase {
// Test line 632: bytecode parameter name fallback
// Temporarily disable the flag to test the bytecode name
fallback
- String originalValue =
System.getProperty("juneau.disableParamNameDetection");
+ String originalValue =
Settings.get().get("juneau.disableParamNameDetection").orElse(null);
try {
- System.setProperty("juneau.disableParamNameDetection",
"false");
+
Settings.get().setLocal("juneau.disableParamNameDetection", "false");
ParameterInfo.reset();
// Get a fresh ParameterInfo instance after resetting
(don't use cached static field)
@@ -680,9 +682,9 @@ class ParameterInfo_Test extends TestBase {
} finally {
// Restore original value
if (originalValue == null)
-
System.clearProperty("juneau.disableParamNameDetection");
+
Settings.get().unsetLocal("juneau.disableParamNameDetection");
else
-
System.setProperty("juneau.disableParamNameDetection", originalValue);
+
Settings.get().setLocal("juneau.disableParamNameDetection", originalValue);
ParameterInfo.reset();
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/settings/PropertySources_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/PropertySources_Test.java
new file mode 100644
index 0000000000..c6a1d9553d
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/PropertySources_Test.java
@@ -0,0 +1,364 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.jar.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.commons.runtime.*;
+import org.junit.jupiter.api.*;
+
+class PropertySources_Test extends TestBase {
+
+
//====================================================================================================
+ // PropertyLookupResult
+
//====================================================================================================
+
+ @Test
+ void a01_result_missing_isNotPresent() {
+ assertFalse(PropertyLookupResult.missing().isPresent());
+ }
+
+ @Test
+ void a02_result_missing_returnsSingleton() {
+ assertSame(PropertyLookupResult.missing(),
PropertyLookupResult.missing());
+ }
+
+ @Test
+ void a03_result_present_withOptionalValue() {
+ var r = PropertyLookupResult.present(Optional.of("hello"));
+ assertTrue(r.isPresent());
+ assertEquals("hello", r.value().orElse(null));
+ }
+
+ @Test
+ void a04_result_present_withOptionalEmpty() {
+ var r = PropertyLookupResult.present(Optional.empty());
+ assertTrue(r.isPresent());
+ assertFalse(r.value().isPresent());
+ }
+
+ @Test
+ void a05_result_present_withStringValue() {
+ // Exercises the present(String) overload directly (not via
present(Optional))
+ var r = PropertyLookupResult.present("world");
+ assertTrue(r.isPresent());
+ assertEquals("world", r.value().orElse(null));
+ }
+
+ @Test
+ void a06_result_present_withNullString() {
+ var r = PropertyLookupResult.present((String) null);
+ assertTrue(r.isPresent());
+ assertFalse(r.value().isPresent());
+ }
+
+ @Test
+ void a07_result_present_nullOptional_throws() {
+ assertThrows(NullPointerException.class, () ->
PropertyLookupResult.present((Optional<String>) null));
+ }
+
+
//====================================================================================================
+ // PropertySourceProvider — default order()
+
//====================================================================================================
+
+ @Test
+ void b01_provider_defaultOrder() {
+ PropertySourceProvider provider = () -> null;
+ assertEquals(0, provider.order());
+ }
+
+
//====================================================================================================
+ // ArgsPropertySource
+
//====================================================================================================
+
+ @Test
+ void c01_args_nullSupplier_missing() {
+ var src = new ArgsPropertySource(() -> null);
+ assertFalse(src.get("anything").isPresent());
+ }
+
+ @Test
+ void c02_args_positionalIndex_found() {
+ var src = new ArgsPropertySource(() -> new Args("pos0 pos1"));
+ var r = src.get("0");
+ assertTrue(r.isPresent());
+ assertEquals("pos0", r.value().orElse(null));
+ }
+
+ @Test
+ void c03_args_positionalIndex_outOfBounds_missing() {
+ var src = new ArgsPropertySource(() -> new Args("pos0"));
+ assertFalse(src.get("5").isPresent());
+ }
+
+ @Test
+ void c04_args_namedOption_found() {
+ var src = new ArgsPropertySource(() -> new Args("-port 8080"));
+ var r = src.get("port");
+ assertTrue(r.isPresent());
+ assertEquals("8080", r.value().orElse(null));
+ }
+
+ @Test
+ void c05_args_namedOption_missing() {
+ var src = new ArgsPropertySource(() -> new Args("-port 8080"));
+ assertFalse(src.get("host").isPresent());
+ }
+
+ @Test
+ void c06_args_multiValueOption_joinedWithComma() {
+ var src = new ArgsPropertySource(() -> new Args("-tag a -tag b
-tag c"));
+ var r = src.get("tag");
+ assertTrue(r.isPresent());
+ assertEquals("a,b,c", r.value().orElse(null));
+ }
+
+ @Test
+ void c07_createDefaultArgs_sunJavaCommand_withArgs() {
+ System.setProperty("sun.java.command", "com.example.Main --port
9090");
+ try {
+ var args = ArgsPropertySource.createDefaultArgs();
+ assertNotNull(args);
+ assertTrue(args.get("port").isPresent());
+ } finally {
+ System.clearProperty("sun.java.command");
+ }
+ }
+
+ @Test
+ void c08_createDefaultArgs_sunJavaCommand_noArgs() {
+ // sun.java.command with no space → empty args string
+ System.setProperty("sun.java.command", "com.example.Main");
+ try {
+ var args = ArgsPropertySource.createDefaultArgs();
+ assertNotNull(args);
+ } finally {
+ System.clearProperty("sun.java.command");
+ }
+ }
+
+ @Test
+ void c09_createDefaultArgs_juneauArgsFallback() {
+ System.clearProperty("sun.java.command");
+ System.setProperty("juneau.args", "--env prod");
+ try {
+ var args = ArgsPropertySource.createDefaultArgs();
+ assertNotNull(args);
+ assertTrue(args.get("env").isPresent());
+ } finally {
+ System.clearProperty("juneau.args");
+ }
+ }
+
+ @Test
+ void c10_createDefaultArgs_bothAbsent() {
+ System.clearProperty("sun.java.command");
+ System.clearProperty("juneau.args");
+ assertNotNull(ArgsPropertySource.createDefaultArgs());
+ }
+
+
//====================================================================================================
+ // DotenvPropertySource
+
//====================================================================================================
+
+ @Test
+ void d01_dotenv_missingFile_returnsEmpty() {
+ var src = new
DotenvPropertySource(Paths.get("nonexistent.env.file.xyz"));
+ assertFalse(src.get("ANY_KEY").isPresent());
+ }
+
+ @Test
+ void d02_dotenv_presentKey_found() throws IOException {
+ var tmp = writeTempDotenv("MY_KEY=hello\n");
+ try {
+ var src = new DotenvPropertySource(tmp);
+ var r = src.get("MY_KEY");
+ assertTrue(r.isPresent());
+ assertEquals("hello", r.value().orElse(null));
+ } finally {
+ Files.deleteIfExists(tmp);
+ }
+ }
+
+ @Test
+ void d03_dotenv_missingKey_missing() throws IOException {
+ var tmp = writeTempDotenv("OTHER_KEY=value\n");
+ try {
+ var src = new DotenvPropertySource(tmp);
+ assertFalse(src.get("MY_KEY").isPresent());
+ } finally {
+ Files.deleteIfExists(tmp);
+ }
+ }
+
+ @Test
+ void d04_dotenv_doubleQuotedValue() throws IOException {
+ var tmp = writeTempDotenv("KEY=\"quoted value\"\n");
+ try {
+ var src = new DotenvPropertySource(tmp);
+ assertEquals("quoted value",
src.get("KEY").value().orElse(null));
+ } finally {
+ Files.deleteIfExists(tmp);
+ }
+ }
+
+ @Test
+ void d05_dotenv_singleQuotedValue() throws IOException {
+ var tmp = writeTempDotenv("KEY='single'\n");
+ try {
+ var src = new DotenvPropertySource(tmp);
+ assertEquals("single",
src.get("KEY").value().orElse(null));
+ } finally {
+ Files.deleteIfExists(tmp);
+ }
+ }
+
+ @Test
+ void d06_dotenv_commentsAndBlankLinesIgnored() throws IOException {
+ var tmp = writeTempDotenv("# comment\n\nKEY=value\n");
+ try {
+ var src = new DotenvPropertySource(tmp);
+ assertEquals("value",
src.get("KEY").value().orElse(null));
+ assertFalse(src.get("# comment").isPresent());
+ } finally {
+ Files.deleteIfExists(tmp);
+ }
+ }
+
+ @Test
+ void d07_dotenv_lineWithNoEquals_ignored() throws IOException {
+ var tmp = writeTempDotenv("INVALID_LINE\nGOOD=ok\n");
+ try {
+ var src = new DotenvPropertySource(tmp);
+ assertFalse(src.get("INVALID_LINE").isPresent());
+ assertEquals("ok",
src.get("GOOD").value().orElse(null));
+ } finally {
+ Files.deleteIfExists(tmp);
+ }
+ }
+
+ @Test
+ void d08_dotenv_resolvePath_fromSystemProperty() throws IOException {
+ var tmp = writeTempDotenv("DOTENV_SYS=found\n");
+ System.setProperty("juneau.dotenv.path", tmp.toString());
+ try {
+ var src = new DotenvPropertySource();
+ assertEquals("found",
src.get("DOTENV_SYS").value().orElse(null));
+ } finally {
+ System.clearProperty("juneau.dotenv.path");
+ Files.deleteIfExists(tmp);
+ }
+ }
+
+ @Test
+ void d09_dotenv_defaultConstructor_doesNotThrow() {
+ // Default path ".env" may not exist; just verify no exception.
+ System.clearProperty("juneau.dotenv.path");
+ var src = new DotenvPropertySource();
+ assertNotNull(src.get("ANYTHING"));
+ }
+
+ @Test
+ void d10_dotenv_emptySystemProperty_fallsToDefault() {
+ // Empty string value → same as absent → falls through to
default ".env" path.
+ System.setProperty("juneau.dotenv.path", "");
+ try {
+ var src = new DotenvPropertySource();
+ assertNotNull(src.get("ANY_KEY")); // no exception,
missing is fine
+ } finally {
+ System.clearProperty("juneau.dotenv.path");
+ }
+ }
+
+ @Test
+ void d11_dotenv_unmatchedQuotes_preservedAsIs() throws IOException {
+ // Value starts with `"` but doesn't end with it → kept as-is
(no strip).
+ var tmp = writeTempDotenv("KEY=\"unmatched\n");
+ try {
+ var src = new DotenvPropertySource(tmp);
+ var r = src.get("KEY");
+ assertTrue(r.isPresent());
+ // The value ends with a newline-stripped but unmatched
quote is kept verbatim.
+ assertTrue(r.value().orElse("").startsWith("\""));
+ } finally {
+ Files.deleteIfExists(tmp);
+ }
+ }
+
+ @Test
+ void d12_dotenv_singleCharValue_quotedBothEnds() throws IOException {
+ // Edge: value is exactly one char between single quotes, e.g.
KEY='' → empty string.
+ var tmp = writeTempDotenv("KEY=''\n");
+ try {
+ var src = new DotenvPropertySource(tmp);
+ assertEquals("", src.get("KEY").value().orElse("x"));
+ } finally {
+ Files.deleteIfExists(tmp);
+ }
+ }
+
+ private static Path writeTempDotenv(String content) throws IOException {
+ var tmp = Files.createTempFile("juneau-test-", ".env");
+ Files.writeString(tmp, content);
+ return tmp;
+ }
+
+
//====================================================================================================
+ // ManifestFilePropertySource
+
//====================================================================================================
+
+ @Test
+ void e01_manifest_nullSupplier_missing() {
+ var src = new ManifestFilePropertySource(() -> null);
+ assertFalse(src.get("Main-Class").isPresent());
+ }
+
+ @Test
+ void e02_manifest_presentKey_found() {
+ var mf = buildManifest("Main-Class", "com.example.Main");
+ var src = new ManifestFilePropertySource(() -> mf);
+ var r = src.get("Main-Class");
+ assertTrue(r.isPresent());
+ assertEquals("com.example.Main", r.value().orElse(null));
+ }
+
+ @Test
+ void e03_manifest_missingKey_missing() {
+ var mf = buildManifest("Main-Class", "com.example.Main");
+ var src = new ManifestFilePropertySource(() -> mf);
+ assertFalse(src.get("Implementation-Version").isPresent());
+ }
+
+ @Test
+ void e04_manifest_createDefault_doesNotThrow() {
+ var src = ManifestFilePropertySource.createDefault();
+ assertNotNull(src);
+ assertNotNull(src.get("Main-Class"));
+ }
+
+ private static ManifestFile buildManifest(String key, String value) {
+ var manifest = new Manifest();
+ manifest.getMainAttributes().putValue(key, value);
+ return new ManifestFile(manifest);
+ }
+}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
index 8c11e49f62..a96e7089b6 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
@@ -667,12 +667,15 @@ class Settings_Test extends TestBase {
}
//====================================================================================================
- // addSource(FunctionalSource) - Functional interface usage
+ // addSource(FunctionalPropertySource) - Functional interface usage
//====================================================================================================
@Test
void p01_addSource_functionalSource() {
- // Test the addSource(FunctionalSource) overload
- var source = (FunctionalSource) name ->
opt(System.getProperty(name));
+ // Test the addSource(FunctionalPropertySource) overload
+ var source = (FunctionalPropertySource) name -> {
+ var value = System.getProperty(name);
+ return value == null ? PropertyLookupResult.missing() :
PropertyLookupResult.present(opt(value));
+ };
System.setProperty(TEST_PROP, "system-value");
var settings = Settings.create()
.addSource(source)
@@ -685,8 +688,8 @@ class Settings_Test extends TestBase {
@Test
void p02_addSource_functionalSource_factoryMethod() {
- // Test addSource with FunctionalSource.of()
- var source = FunctionalSource.of(System::getProperty);
+ // Test addSource with FunctionalPropertySource.of()
+ var source = FunctionalPropertySource.of(System::getProperty);
System.setProperty(TEST_PROP, "system-value");
var settings = Settings.create()
.addSource(source)
@@ -698,12 +701,12 @@ class Settings_Test extends TestBase {
}
//====================================================================================================
- // FunctionalStore
+ // FunctionalPropertyStore
//====================================================================================================
@Test
- void q01_writeableFunctionalSource_basic() {
+ void q01_writeableFunctionalPropertySource_basic() {
// Create a writable functional source using system properties
- var source = FunctionalStore.of(
+ var source = FunctionalPropertyStore.of(
System::getProperty,
System::setProperty,
System::clearProperty,
@@ -715,21 +718,21 @@ class Settings_Test extends TestBase {
var result = source.get(TEST_PROP);
assertNotNull(result);
assertTrue(result.isPresent());
- assertEquals("test-value", result.get());
+ assertEquals("test-value", result.value().orElse(null));
// Test unset
source.unset(TEST_PROP);
result = source.get(TEST_PROP);
- assertNull(result); // Should return null when property doesn't
exist
+ assertFalse(result.isPresent()); // Should report missing when
property doesn't exist
// Clean up
System.clearProperty(TEST_PROP);
}
@Test
- void q02_writeableFunctionalSource_withSettings() {
+ void q02_writeableFunctionalPropertySource_withSettings() {
// Create a writable functional source and add it to Settings
- var source = FunctionalStore.of(
+ var source = FunctionalPropertyStore.of(
System::getProperty,
System::setProperty,
System::clearProperty,
@@ -756,10 +759,10 @@ class Settings_Test extends TestBase {
}
@Test
- void q03_writeableFunctionalSource_clear() {
+ void q03_writeableFunctionalPropertySource_clear() {
// Test clear() functionality with a custom clearer
var map = new java.util.HashMap<String, String>();
- var source = FunctionalStore.of(
+ var source = FunctionalPropertyStore.of(
map::get,
map::put,
map::remove,
@@ -773,22 +776,22 @@ class Settings_Test extends TestBase {
var result1 = source.get(TEST_PROP);
assertNotNull(result1);
assertTrue(result1.isPresent());
- assertEquals("test-value", result1.get());
+ assertEquals("test-value", result1.value().orElse(null));
var result2 = source.get(TEST_PROP_2);
assertNotNull(result2);
assertTrue(result2.isPresent());
- assertEquals("test-value-2", result2.get());
+ assertEquals("test-value-2", result2.value().orElse(null));
// Clear all values
source.clear();
// Verify values are cleared
result1 = source.get(TEST_PROP);
- assertNull(result1);
+ assertFalse(result1.isPresent());
result2 = source.get(TEST_PROP_2);
- assertNull(result2);
+ assertFalse(result2.isPresent());
}
//====================================================================================================
@@ -867,9 +870,9 @@ class Settings_Test extends TestBase {
store.unset(TEST_PROP);
// Verify the map is still null (not initialized)
- // get() should return null since the map doesn't exist
+ // get() should report missing since the map doesn't exist
var result = store.get(TEST_PROP);
- assertNull(result);
+ assertFalse(result.isPresent());
}
//====================================================================================================
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/svl/vars/PropertyVars_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/svl/vars/PropertyVars_Test.java
new file mode 100644
index 0000000000..9bee2df31a
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/svl/vars/PropertyVars_Test.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.svl.vars;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.jar.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.commons.runtime.*;
+import org.apache.juneau.commons.settings.*;
+import org.apache.juneau.commons.svl.*;
+import org.junit.jupiter.api.*;
+
+class PropertyVars_Test extends TestBase {
+
+
//====================================================================================================
+ // ArgsVar
+
//====================================================================================================
+
+ @Test
+ void a01_argsVar_init_resolvesPositional() {
+ ArgsVar.init(new Args("hello world"));
+ var vr = VarResolver.create().vars(ArgsVar.class).build();
+ assertEquals("hello", vr.resolve("$A{0}"));
+ assertEquals("world", vr.resolve("$A{1}"));
+ }
+
+ @Test
+ void a02_argsVar_init_resolvesNamed() {
+ ArgsVar.init(new Args("-port 9999"));
+ var vr = VarResolver.create().vars(ArgsVar.class).build();
+ assertEquals("9999", vr.resolve("$A{port}"));
+ }
+
+ @Test
+ void a03_argsVar_init_missingKey_returnsDefault() {
+ ArgsVar.init(new Args("-port 9999"));
+ var vr = VarResolver.create().vars(ArgsVar.class).build();
+ assertEquals("defaultVal", vr.resolve("$A{host,defaultVal}"));
+ }
+
+ @Test
+ void a04_argsVar_create_withSupplier() {
+ var src = ArgsVar.create(() -> new Args("-env staging"));
+ var vr = VarResolver.create().vars(src).build();
+ assertEquals("staging", vr.resolve("$A{env}"));
+ }
+
+ @Test
+ void a05_argsVar_create_missingKey_returnsDefault() {
+ var src = ArgsVar.create(() -> new Args("-env staging"));
+ var vr = VarResolver.create().vars(src).build();
+ assertEquals("prod", vr.resolve("$A{region,prod}"));
+ }
+
+
//====================================================================================================
+ // ManifestFileVar
+
//====================================================================================================
+
+ @Test
+ void b01_manifestFileVar_init_resolvesKey() {
+ var manifest = new Manifest();
+ manifest.getMainAttributes().putValue("My-Attr",
"from-manifest");
+ ManifestFileVar.init(new ManifestFile(manifest));
+ var vr =
VarResolver.create().vars(ManifestFileVar.class).build();
+ assertEquals("from-manifest", vr.resolve("$MF{My-Attr}"));
+ }
+
+ @Test
+ void b02_manifestFileVar_init_missingKey_returnsEmptyString() {
+ // ManifestFileVar.resolve() returns "" (not null) for missing
keys,
+ // so DefaultingVar does not apply the default — empty string
is returned.
+ var manifest = new Manifest();
+ manifest.getMainAttributes().putValue("My-Attr", "x");
+ ManifestFileVar.init(new ManifestFile(manifest));
+ var vr =
VarResolver.create().vars(ManifestFileVar.class).build();
+ assertEquals("", vr.resolve("$MF{Missing-Attr}"));
+ assertEquals("",
vr.resolve("$MF{Missing-Attr,ignored-default}"));
+ }
+
+ @Test
+ void b03_manifestFileVar_create_withSupplier() {
+ var manifest = new Manifest();
+ manifest.getMainAttributes().putValue("Version", "1.2.3");
+ var mf = new ManifestFile(manifest);
+ var src = ManifestFileVar.create(() -> mf);
+ var vr = VarResolver.create().vars(src).build();
+ assertEquals("1.2.3", vr.resolve("$MF{Version}"));
+ }
+
+ @Test
+ void b04_manifestFileVar_nullManifest_returnsEmptyString() {
+ var src = ManifestFileVar.create(() -> null);
+ var vr = VarResolver.create().vars(src).build();
+ assertEquals("", vr.resolve("$MF{Main-Class}"));
+ }
+
+
//====================================================================================================
+ // PropertyVar
+
//====================================================================================================
+
+ @Test
+ void c01_propertyVar_resolvesFromSystemProperty() {
+ System.setProperty("PropertyVars_Test.c01", "sysval");
+ try {
+ var vr =
VarResolver.create().vars(PropertyVar.class).build();
+ assertEquals("sysval",
vr.resolve("$P{PropertyVars_Test.c01}"));
+ } finally {
+ System.clearProperty("PropertyVars_Test.c01");
+ }
+ }
+
+ @Test
+ void c02_propertyVar_missingKey_returnsDefault() {
+ System.clearProperty("PropertyVars_Test.c02.missing");
+ var vr = VarResolver.create().vars(PropertyVar.class).build();
+ assertEquals("defval",
vr.resolve("$P{PropertyVars_Test.c02.missing,defval}"));
+ }
+
+ @Test
+ void c03_propertyVar_fromSettings() {
+ Settings.get().setGlobal("PropertyVars_Test.c03",
"overrideVal");
+ try {
+ var vr =
VarResolver.create().vars(PropertyVar.class).build();
+ assertEquals("overrideVal",
vr.resolve("$P{PropertyVars_Test.c03}"));
+ } finally {
+ Settings.get().unsetGlobal("PropertyVars_Test.c03");
+ }
+ }
+}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/config/ConfigPropertySource_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigPropertySource_Test.java
new file mode 100644
index 0000000000..0c2a968fde
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigPropertySource_Test.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.config;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.config.store.*;
+import org.junit.jupiter.api.*;
+
+class ConfigPropertySource_Test extends TestBase {
+
+ private Config build(String...lines) {
+ MemoryStore.DEFAULT.update("Test.cfg", lines);
+ return
Config.create().store(MemoryStore.DEFAULT).name("Test.cfg").build();
+ }
+
+ @Test
+ void a01_nullConfig_missing() {
+ var src = new ConfigPropertySource(null);
+ assertFalse(src.get("any.key").isPresent());
+ }
+
+ @Test
+ void a02_presentKey_found() {
+ var cfg = build("key = value");
+ var src = new ConfigPropertySource(cfg);
+ var r = src.get("key");
+ assertTrue(r.isPresent());
+ assertEquals("value", r.value().orElse(null));
+ }
+
+ @Test
+ void a03_missingKey_missing() {
+ var cfg = build("other = value");
+ var src = new ConfigPropertySource(cfg);
+ assertFalse(src.get("key").isPresent());
+ }
+
+ @Test
+ void a04_keyInSection_found() {
+ var cfg = build("[section]", "key = hello");
+ var src = new ConfigPropertySource(cfg);
+ var r = src.get("section/key");
+ assertTrue(r.isPresent());
+ assertEquals("hello", r.value().orElse(null));
+ }
+
+ @Test
+ void a05_exception_treatedAsMissing() {
+ // Passing a null config exercises the null guard, not the
catch block.
+ // We exercise the catch by using a key path that
Config.getString may reject.
+ var cfg = build();
+ var src = new ConfigPropertySource(cfg);
+ // An empty string key is unusual; whatever happens, no
exception should propagate.
+ assertNotNull(src.get(""));
+ }
+}