diff --git a/README.md b/README.md
index dacf9b5..7e7e751 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,198 @@
-Example WebApp Plugin for Fess
+# Fess WebApp Plugin Example
+
[](https://github.com/codelibs/fess-webapp-example/actions/workflows/maven.yml)
-==========================
+[](https://maven-badges.herokuapp.com/maven-central/org.codelibs.fess/fess-webapp-example)
+[](https://opensource.org/licenses/Apache-2.0)
+
+A demonstration WebApp plugin for [Fess](https://fess.codelibs.org/), showing how to create custom JSP design templates and extend the search engine's web interface functionality.
## Overview
-This is a sample plugin for Fess webapp.
+This plugin demonstrates how to extend Fess's web application layer by providing custom JSP templates for various UI components. It serves as a practical example for developers who want to create their own custom web interfaces for Fess search applications.
+
+### Key Features
+
+- **Custom JSP Templates**: Provides custom design templates for search pages, navigation, and error handling
+- **System Helper Extension**: Extends Fess's `SystemHelper` class with enhanced error handling and logging
+- **Component Registration**: Demonstrates dependency injection configuration using LastaDi framework
+- **Comprehensive UI Coverage**: Includes templates for search interface, user management, and error pages
+
+## Supported UI Components
+
+The plugin registers custom JSP templates for the following components:
+
+### Search Interface
+- `index.jsp` - Main search page
+- `search.jsp` - Search interface
+- `searchResults.jsp` - Search results display
+- `searchNoResult.jsp` - No results found page
+- `searchOptions.jsp` - Search options
+- `advance.jsp` - Advanced search
+- `help.jsp` - Help page
-## Download
+### Navigation & Layout
+- `header.jsp` - Page header
+- `footer.jsp` - Page footer
-See [Maven Repository](https://repo1.maven.org/maven2/org/codelibs/fess/fess-webapp-example/).
+### Error Handling
+- `error/error.jsp` - General error page
+- `error/notFound.jsp` - 404 Not Found
+- `error/system.jsp` - System error
+- `error/redirect.jsp` - Redirect error
+- `error/badRequest.jsp` - 400 Bad Request
+
+### User Interface
+- `login/index.jsp` - Login page
+- `profile/index.jsp` - User profile page
+
+### Cache Display
+- `cache.hbs` - Cache display template (Handlebars)
+
+## Requirements
+
+- Java 21 or later
+- Maven 3.6 or later
+- Fess 15.0 or later
## Installation
-See [Plugin](https://fess.codelibs.org/13.9/admin/plugin-guide.html) of Administration guide.
+### From Maven Repository
+
+The plugin is available on Maven Central:
+
+```xml
+
+ org.codelibs.fess
+ fess-webapp-example
+ 15.0.0
+
+```
+
+### Manual Installation
+
+1. Download the plugin JAR from [Maven Repository](https://repo1.maven.org/maven2/org/codelibs/fess/fess-webapp-example/)
+2. Follow the [Plugin Installation Guide](https://fess.codelibs.org/admin/plugin-guide.html) in the Fess documentation
+
+### Building from Source
+
+```bash
+git clone https://github.com/codelibs/fess-webapp-example.git
+cd fess-webapp-example
+mvn clean package
+```
+
+The compiled JAR will be available in the `target/` directory.
+
+## Development
+
+### Project Structure
+
+```
+src/
+├── main/
+│ ├── java/
+│ │ └── org/codelibs/fess/plugin/webapp/helper/
+│ │ └── CustomSystemHelper.java
+│ └── resources/
+│ └── fess+systemHelper.xml
+└── test/
+ ├── java/
+ │ └── org/codelibs/fess/plugin/webapp/helper/
+ │ └── CustomSystemHelperTest.java
+ └── resources/
+ └── test_app.xml
+```
+
+### Core Components
+
+#### CustomSystemHelper
+
+The main plugin class that extends Fess's `SystemHelper`:
+
+- **Location**: `src/main/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelper.java`
+- **Function**: Overrides `parseProjectProperties()` with enhanced error handling
+- **System Property**: Sets `fess.webapp.plugin=true` during initialization
+
+#### Configuration
+
+- **DI Configuration**: `src/main/resources/fess+systemHelper.xml`
+- **Component Registration**: Maps UI component names to JSP template files
+- **Test Configuration**: `src/test/resources/test_app.xml`
+
+### Building and Testing
+
+```bash
+# Compile the project
+mvn clean compile
+
+# Run tests
+mvn test
+
+# Create package
+mvn clean package
+
+# Format code
+mvn formatter:format
+
+# Check license headers
+mvn license:check
+
+# Generate documentation
+mvn javadoc:javadoc
+```
+
+### Creating Custom Templates
+
+1. Extend the `CustomSystemHelper` class or create your own helper
+2. Register your JSP templates in the DI configuration file
+3. Ensure your plugin JAR includes the manifest entry: `Fess-WebAppJar=true`
+
+## Configuration
+
+The plugin uses LastaDi dependency injection framework. Template mappings are configured in `fess+systemHelper.xml`:
+
+```xml
+
+
+ "index"
+ "index.jsp"
+
+
+
+```
+
+## Contributing
+
+1. Fork the repository
+2. Create a feature branch (`git checkout -b feature/your-feature`)
+3. Commit your changes (`git commit -am 'Add some feature'`)
+4. Push to the branch (`git push origin feature/your-feature`)
+5. Create a Pull Request
+
+### Development Guidelines
+
+- Follow the existing code style and conventions
+- Add appropriate test cases for new functionality
+- Ensure all tests pass before submitting
+- Update documentation as needed
+
+## License
+
+This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
+
+## Support
+
+- **Documentation**: [Fess Documentation](https://fess.codelibs.org/)
+- **Plugin Guide**: [Plugin Installation Guide](https://fess.codelibs.org/admin/plugin-guide.html)
+- **Issues**: [GitHub Issues](https://github.com/codelibs/fess-webapp-example/issues)
+- **Discussions**: [GitHub Discussions](https://github.com/codelibs/fess-webapp-example/discussions)
+
+## Related Projects
+
+- [Fess](https://github.com/codelibs/fess) - The main Fess search server
+- [LastaFlute](https://github.com/lastaflute/lastaflute) - Web framework used by Fess
+- [DBFlute](https://github.com/dbflute/dbflute-core) - Database access framework
+
+---
+**CodeLibs Project** - https://www.codelibs.org/
\ No newline at end of file
diff --git a/src/main/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelper.java b/src/main/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelper.java
index 9076823..1174776 100644
--- a/src/main/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelper.java
+++ b/src/main/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelper.java
@@ -21,8 +21,21 @@
import org.apache.logging.log4j.Logger;
import org.codelibs.fess.helper.SystemHelper;
+/**
+ * Custom system helper for Fess webapp plugin that extends the default SystemHelper.
+ * This helper enables webapp plugin functionality by setting the appropriate system property
+ * and provides enhanced error handling for project properties parsing.
+ */
public class CustomSystemHelper extends SystemHelper {
+ /**
+ * Default constructor for CustomSystemHelper.
+ * Initializes the custom system helper with webapp plugin capabilities.
+ */
+ public CustomSystemHelper() {
+ super();
+ }
+
private static final Logger logger = LogManager.getLogger(CustomSystemHelper.class);
@Override
diff --git a/src/test/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelperTest.java b/src/test/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelperTest.java
index 5f69702..15bfecb 100644
--- a/src/test/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelperTest.java
+++ b/src/test/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelperTest.java
@@ -15,6 +15,14 @@
*/
package org.codelibs.fess.plugin.webapp.helper;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.LoggerConfig;
import org.codelibs.fess.mylasta.direction.FessConfig;
import org.codelibs.fess.util.ComponentUtil;
import org.dbflute.utflute.lastaflute.LastaFluteTestCase;
@@ -64,4 +72,141 @@ public void tearDown() throws Exception {
public void test_checkProperty() {
assertEquals("true", System.getProperty("fess.webapp.plugin"));
}
+
+ public void test_parseProjectProperties_withValidPath() {
+ // Given
+ CustomSystemHelper helper = new CustomSystemHelper();
+ Path validPath = Paths.get("src/test/resources/test_app.xml");
+
+ // When
+ helper.parseProjectProperties(validPath);
+
+ // Then
+ assertEquals("true", System.getProperty("fess.webapp.plugin"));
+ }
+
+ public void test_parseProjectProperties_withNullPath() {
+ // Given
+ CustomSystemHelper helper = new CustomSystemHelper();
+
+ // When
+ helper.parseProjectProperties(null);
+
+ // Then
+ assertEquals("true", System.getProperty("fess.webapp.plugin"));
+ }
+
+ public void test_parseProjectProperties_withNonExistentPath() {
+ // Given
+ CustomSystemHelper helper = new CustomSystemHelper();
+ Path nonExistentPath = Paths.get("non/existent/path/project.properties");
+
+ // When
+ helper.parseProjectProperties(nonExistentPath);
+
+ // Then
+ assertEquals("true", System.getProperty("fess.webapp.plugin"));
+ }
+
+ public void test_parseProjectProperties_systemPropertyAlwaysSet() {
+ // Given
+ CustomSystemHelper helper = new CustomSystemHelper();
+ System.clearProperty("fess.webapp.plugin");
+ assertNull("Property should be cleared initially", System.getProperty("fess.webapp.plugin"));
+
+ // When
+ helper.parseProjectProperties(Paths.get("invalid/path"));
+
+ // Then
+ assertEquals("true", System.getProperty("fess.webapp.plugin"));
+ }
+
+ public void test_parseProjectProperties_multipleCalls() {
+ // Given
+ CustomSystemHelper helper = new CustomSystemHelper();
+ Path testPath = Paths.get("src/test/resources/test_app.xml");
+
+ // When
+ helper.parseProjectProperties(testPath);
+ helper.parseProjectProperties(testPath);
+ helper.parseProjectProperties(null);
+
+ // Then
+ assertEquals("true", System.getProperty("fess.webapp.plugin"));
+ }
+
+ public void test_inheritance_extendsSystemHelper() {
+ // Given
+ CustomSystemHelper helper = new CustomSystemHelper();
+
+ // Then
+ assertTrue("CustomSystemHelper should extend SystemHelper", helper instanceof org.codelibs.fess.helper.SystemHelper);
+ }
+
+ public void test_loggerConfiguration() {
+ // Given
+ LoggerContext context = (LoggerContext) LogManager.getContext(false);
+ Configuration config = context.getConfiguration();
+ LoggerConfig loggerConfig = config.getLoggerConfig(CustomSystemHelper.class.getName());
+
+ // Then
+ assertNotNull("Logger should be configured", loggerConfig);
+ }
+
+ public void test_parseProjectProperties_withEmptyPath() {
+ // Given
+ CustomSystemHelper helper = new CustomSystemHelper();
+ Path emptyPath = Paths.get("");
+
+ // When
+ helper.parseProjectProperties(emptyPath);
+
+ // Then
+ assertEquals("true", System.getProperty("fess.webapp.plugin"));
+ }
+
+ public void test_parseProjectProperties_propertyPersistence() {
+ // Given
+ CustomSystemHelper helper1 = new CustomSystemHelper();
+ CustomSystemHelper helper2 = new CustomSystemHelper();
+ System.clearProperty("fess.webapp.plugin");
+
+ // When
+ helper1.parseProjectProperties(null);
+ String propertyAfterFirst = System.getProperty("fess.webapp.plugin");
+ helper2.parseProjectProperties(null);
+ String propertyAfterSecond = System.getProperty("fess.webapp.plugin");
+
+ // Then
+ assertEquals("true", propertyAfterFirst);
+ assertEquals("true", propertyAfterSecond);
+ assertEquals("Property should remain consistent", propertyAfterFirst, propertyAfterSecond);
+ }
+
+ public void test_parseProjectProperties_threadSafety() throws InterruptedException {
+ // Given
+ final CustomSystemHelper helper = new CustomSystemHelper();
+ final int threadCount = 10;
+ Thread[] threads = new Thread[threadCount];
+ final boolean[] results = new boolean[threadCount];
+
+ // When
+ for (int i = 0; i < threadCount; i++) {
+ final int index = i;
+ threads[i] = new Thread(() -> {
+ helper.parseProjectProperties(null);
+ results[index] = "true".equals(System.getProperty("fess.webapp.plugin"));
+ });
+ threads[i].start();
+ }
+
+ for (Thread thread : threads) {
+ thread.join();
+ }
+
+ // Then
+ for (boolean result : results) {
+ assertTrue("All threads should see the property set", result);
+ }
+ }
}