This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 1b518f1d8fae99ae86496003dc4fd76f65ffffdb
Author: Lukasz Lenart <[email protected]>
AuthorDate: Fri Oct 23 12:43:08 2026 +0200

    WW-5791 feat(boot): read port and context path from struts.properties and 
arguments
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../java/org/apache/struts2/boot/BootSettings.java | 95 ++++++++++++++++++++++
 .../org/apache/struts2/boot/BootSettingsTest.java  | 77 ++++++++++++++++++
 2 files changed, 172 insertions(+)

diff --git a/boot/src/main/java/org/apache/struts2/boot/BootSettings.java 
b/boot/src/main/java/org/apache/struts2/boot/BootSettings.java
new file mode 100644
index 000000000..7632a4694
--- /dev/null
+++ b/boot/src/main/java/org/apache/struts2/boot/BootSettings.java
@@ -0,0 +1,95 @@
+/*
+ * 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.struts2.boot;
+
+import org.apache.struts2.config.ConfigurationException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public final class BootSettings {
+
+    private static final String PREFIX = "struts.boot.";
+    private static final String PORT = PREFIX + "port";
+    private static final String CONTEXT_PATH = PREFIX + "context-path";
+    private static final String ARGUMENT_PREFIX = "--";
+
+    private final int port;
+    private final String contextPath;
+
+    private BootSettings(int port, String contextPath) {
+        this.port = port;
+        this.contextPath = contextPath;
+    }
+
+    public static BootSettings load(String[] args) {
+        return load(readStrutsProperties(), args);
+    }
+
+    public static BootSettings load(Properties fileProperties, String[] args) {
+        Properties merged = new Properties();
+        merged.putAll(fileProperties);
+        for (String arg : args) {
+            applyArgument(merged, arg);
+        }
+        return new BootSettings(
+                parsePort(merged.getProperty(PORT, "8080")),
+                merged.getProperty(CONTEXT_PATH, ""));
+    }
+
+    public int port() {
+        return port;
+    }
+
+    public String contextPath() {
+        return contextPath;
+    }
+
+    private static void applyArgument(Properties target, String arg) {
+        if (!arg.startsWith(ARGUMENT_PREFIX + PREFIX)) {
+            return;
+        }
+        int equals = arg.indexOf('=');
+        if (equals < 0) {
+            return;
+        }
+        target.setProperty(arg.substring(ARGUMENT_PREFIX.length(), equals), 
arg.substring(equals + 1));
+    }
+
+    private static int parsePort(String value) {
+        try {
+            return Integer.parseInt(value.trim());
+        } catch (NumberFormatException e) {
+            throw new ConfigurationException("Invalid " + PORT + ": '" + value 
+ "' is not a number", e);
+        }
+    }
+
+    private static Properties readStrutsProperties() {
+        Properties properties = new Properties();
+        try (InputStream in = 
Thread.currentThread().getContextClassLoader().getResourceAsStream("struts.properties"))
 {
+            if (in != null) {
+                properties.load(in);
+            }
+        } catch (IOException e) {
+            throw new ConfigurationException("Cannot read struts.properties", 
e);
+        }
+        return properties;
+    }
+}
diff --git a/boot/src/test/java/org/apache/struts2/boot/BootSettingsTest.java 
b/boot/src/test/java/org/apache/struts2/boot/BootSettingsTest.java
new file mode 100644
index 000000000..f4fc3b7fb
--- /dev/null
+++ b/boot/src/test/java/org/apache/struts2/boot/BootSettingsTest.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.struts2.boot;
+
+import org.apache.struts2.config.ConfigurationException;
+import org.junit.Test;
+
+import java.util.Properties;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class BootSettingsTest {
+
+    @Test
+    public void testDefaults() {
+        BootSettings settings = BootSettings.load(new Properties(), new 
String[0]);
+
+        assertThat(settings.port()).isEqualTo(8080);
+        assertThat(settings.contextPath()).isEmpty();
+    }
+
+    @Test
+    public void testFileValue() {
+        Properties file = new Properties();
+        file.setProperty("struts.boot.port", "8081");
+
+        assertThat(BootSettings.load(file, new 
String[0]).port()).isEqualTo(8081);
+    }
+
+    @Test
+    public void testArgumentBeatsFile() {
+        Properties file = new Properties();
+        file.setProperty("struts.boot.port", "8081");
+
+        BootSettings settings = BootSettings.load(file, new 
String[]{"--struts.boot.port=9090"});
+
+        assertThat(settings.port()).isEqualTo(9090);
+    }
+
+    @Test
+    public void testUnknownArgumentsIgnored() {
+        BootSettings settings = BootSettings.load(new Properties(), new 
String[]{"--foo=bar", "positional"});
+
+        assertThat(settings.port()).isEqualTo(8080);
+        assertThat(settings.contextPath()).isEmpty();
+    }
+
+    @Test
+    public void testContextPathFromArgs() {
+        BootSettings settings = BootSettings.load(new Properties(), new 
String[]{"--struts.boot.context-path=/ctx"});
+
+        assertThat(settings.contextPath()).isEqualTo("/ctx");
+    }
+
+    @Test
+    public void testNonNumericPortThrows() {
+        assertThatThrownBy(() -> BootSettings.load(new Properties(), new 
String[]{"--struts.boot.port=abc"}))
+                .isInstanceOf(ConfigurationException.class);
+    }
+}

Reply via email to