diff --git a/apps/faces-example2/pom.xml b/apps/faces-example2/pom.xml
index 54b35398e..b5521b67f 100644
--- a/apps/faces-example2/pom.xml
+++ b/apps/faces-example2/pom.xml
@@ -174,6 +174,20 @@
3.2.2
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 3.2.0
+
+
+
+ repackage
+
+
+
+
+
org.jacoco
diff --git a/apps/faces-example2/src/test/java/org/apache/struts/webapp/example2/TestApplication.java b/apps/faces-example2/src/main/java/org/apache/struts/webapp/example2/Example2Application.java
similarity index 57%
rename from apps/faces-example2/src/test/java/org/apache/struts/webapp/example2/TestApplication.java
rename to apps/faces-example2/src/main/java/org/apache/struts/webapp/example2/Example2Application.java
index f2a9f5073..b73b347fd 100644
--- a/apps/faces-example2/src/test/java/org/apache/struts/webapp/example2/TestApplication.java
+++ b/apps/faces-example2/src/main/java/org/apache/struts/webapp/example2/Example2Application.java
@@ -16,11 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package org.apache.struts.webapp.example2;
+import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+/**
+ * Spring Boot main application class for the Struts Faces Example 2 migration.
+ * This class serves as the entry point for the Spring Boot application,
+ * replacing the traditional web.xml and servlet configuration.
+ *
+ * The @SpringBootApplication annotation enables:
+ * - @Configuration: Tags the class as a source of bean definitions
+ * - @EnableAutoConfiguration: Enables Spring Boot's auto-configuration
+ * - @ComponentScan: Enables component scanning in the current package and sub-packages
+ */
@SpringBootApplication
-public class TestApplication {
+public class Example2Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Example2Application.class, args);
+ }
}
diff --git a/apps/faces-example2/src/main/java/org/apache/struts/webapp/example2/config/DatabaseConfiguration.java b/apps/faces-example2/src/main/java/org/apache/struts/webapp/example2/config/DatabaseConfiguration.java
index c04fc30e7..f7a7047e1 100644
--- a/apps/faces-example2/src/main/java/org/apache/struts/webapp/example2/config/DatabaseConfiguration.java
+++ b/apps/faces-example2/src/main/java/org/apache/struts/webapp/example2/config/DatabaseConfiguration.java
@@ -18,14 +18,136 @@
*/
package org.apache.struts.webapp.example2.config;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
import java.util.List;
+import jakarta.annotation.PreDestroy;
+
+import org.apache.struts.webapp.example2.UserDatabase;
+import org.apache.struts.webapp.example2.memory.MemoryUserDatabase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+/**
+ * Spring configuration class that replaces the MemoryDatabasePlugIn from Struts.
+ * This class initializes the in-memory user database from an XML file and provides
+ * it as a Spring bean for dependency injection.
+ */
@Configuration
public class DatabaseConfiguration {
+ private static final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class);
+
+ @Value("${app.database.path:classpath:database.xml}")
+ private Resource databaseResource;
+
+ private MemoryUserDatabase database;
+
+ /**
+ * Creates and initializes the UserDatabase bean.
+ * The database is loaded from the configured XML file path.
+ *
+ * @return The initialized UserDatabase instance
+ * @throws Exception if the database cannot be loaded
+ */
+ @Bean
+ public UserDatabase userDatabase() throws Exception {
+ log.info("Initializing memory database from '{}'", databaseResource);
+
+ database = new MemoryUserDatabase();
+
+ try {
+ if (databaseResource.exists() && databaseResource.isFile()) {
+ File file = databaseResource.getFile();
+ database.setPathname(file.getAbsolutePath());
+ database.open();
+ } else if (databaseResource.exists()) {
+ File tempFile = createTempDatabaseFile();
+ database.setPathname(tempFile.getAbsolutePath());
+ try (InputStream is = databaseResource.getInputStream()) {
+ copyInputStreamToFile(is, tempFile);
+ }
+ database.open();
+ } else {
+ log.warn("Database resource '{}' does not exist, creating empty database", databaseResource);
+ File tempFile = createTempDatabaseFile();
+ database.setPathname(tempFile.getAbsolutePath());
+ createEmptyDatabaseFile(tempFile);
+ database.open();
+ }
+ } catch (Exception e) {
+ log.error("Failed to initialize database from '{}': {}", databaseResource, e.getMessage());
+ throw e;
+ }
+
+ log.info("Memory database initialized successfully");
+ return database;
+ }
+
+ /**
+ * Creates a temporary file for the database when running from a JAR.
+ * This allows the database to be saved during runtime.
+ *
+ * @return The temporary file
+ * @throws IOException if the file cannot be created
+ */
+ private File createTempDatabaseFile() throws IOException {
+ File tempDir = Files.createTempDirectory("struts-example2").toFile();
+ tempDir.deleteOnExit();
+ File tempFile = new File(tempDir, "database.xml");
+ tempFile.deleteOnExit();
+ return tempFile;
+ }
+
+ /**
+ * Copies an input stream to a file.
+ *
+ * @param is The input stream
+ * @param file The target file
+ * @throws IOException if copying fails
+ */
+ private void copyInputStreamToFile(InputStream is, File file) throws IOException {
+ Files.copy(is, file.toPath());
+ }
+
+ /**
+ * Creates a database XML file with sample data.
+ * This provides initial test data for the application.
+ *
+ * @param file The file to create
+ * @throws IOException if file creation fails
+ */
+ private void createEmptyDatabaseFile(File file) throws IOException {
+ String sampleDb = """
+
+
+
+
+
+
+
+ """;
+ Files.writeString(file.toPath(), sampleDb);
+ }
+
+ /**
+ * Provides a list of server types for subscription forms.
+ * This replaces the setupCache method from MemoryDatabasePlugIn.
+ *
+ * @return List of server type options
+ */
@Bean
public List serverTypes() {
return List.of(
@@ -34,6 +156,26 @@ public List serverTypes() {
);
}
+ /**
+ * Cleanup method called when the application shuts down.
+ * Saves and closes the database.
+ */
+ @PreDestroy
+ public void cleanup() {
+ log.info("Finalizing memory database");
+ if (database != null) {
+ try {
+ database.close();
+ } catch (Exception e) {
+ log.error("Error closing memory database: {}", e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Simple bean to hold label-value pairs for dropdown options.
+ * This replaces the Struts LabelValueBean.
+ */
public static class LabelValueBean {
private final String label;
private final String value;
diff --git a/apps/faces-example2/src/main/java/org/apache/struts/webapp/example2/controller/WelcomeController.java b/apps/faces-example2/src/main/java/org/apache/struts/webapp/example2/controller/WelcomeController.java
new file mode 100644
index 000000000..9bbfc3eaf
--- /dev/null
+++ b/apps/faces-example2/src/main/java/org/apache/struts/webapp/example2/controller/WelcomeController.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.struts.webapp.example2.controller;
+
+import jakarta.servlet.http.HttpSession;
+
+import org.apache.struts.webapp.example2.Constants;
+import org.apache.struts.webapp.example2.User;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@Controller
+public class WelcomeController {
+
+ @GetMapping({"/", "/welcome"})
+ public String welcome(HttpSession session, Model model) {
+ User user = (User) session.getAttribute(Constants.USER_KEY);
+ if (user != null) {
+ model.addAttribute("user", user);
+ }
+ return "welcome";
+ }
+
+ @GetMapping("/mainMenu")
+ public String mainMenu(HttpSession session, Model model) {
+ User user = (User) session.getAttribute(Constants.USER_KEY);
+ if (user == null) {
+ return "redirect:/editLogon";
+ }
+ model.addAttribute("user", user);
+ return "mainMenu";
+ }
+}
diff --git a/apps/faces-example2/src/main/resources/application.properties b/apps/faces-example2/src/main/resources/application.properties
index ce3bf3461..98f11aaf4 100644
--- a/apps/faces-example2/src/main/resources/application.properties
+++ b/apps/faces-example2/src/main/resources/application.properties
@@ -11,3 +11,11 @@ spring.thymeleaf.suffix=.html
# Message source configuration
spring.messages.basename=messages
spring.messages.encoding=UTF-8
+
+# Database configuration
+# Uses the existing database.xml from webapp/WEB-INF for sample data
+app.database.path=classpath:database.xml
+
+# Logging
+logging.level.org.apache.struts.webapp.example2=DEBUG
+logging.level.org.springframework.web=INFO