From 717c7c07f5571fe80140a5c72f4d234ba2f9574b Mon Sep 17 00:00:00 2001 From: Shinsuke Sugaya Date: Thu, 25 Jun 2026 23:24:24 +0900 Subject: [PATCH 1/2] Modernize example webapp helper (CustomSystemHelper -> ExampleHelper) --- README.md | 216 ++++----- pom.xml | 4 +- .../webapp/helper/CustomSystemHelper.java | 50 -- .../webapp/example/helper/ExampleHelper.java | 101 +++++ src/main/resources/app++.xml | 18 + src/main/resources/fess+systemHelper.xml | 75 --- .../webapp/helper/CustomSystemHelperTest.java | 429 ------------------ .../example/helper/ExampleHelperTest.java | 61 +++ src/test/resources/test_app.xml | 5 +- 9 files changed, 277 insertions(+), 682 deletions(-) delete mode 100644 src/main/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelper.java create mode 100644 src/main/java/org/codelibs/fess/webapp/example/helper/ExampleHelper.java create mode 100644 src/main/resources/app++.xml delete mode 100644 src/main/resources/fess+systemHelper.xml delete mode 100644 src/test/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelperTest.java create mode 100644 src/test/java/org/codelibs/fess/webapp/example/helper/ExampleHelperTest.java diff --git a/README.md b/README.md index 7e7e751..d32bdc5 100644 --- a/README.md +++ b/README.md @@ -4,162 +4,144 @@ [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.codelibs.fess/fess-webapp-example/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.codelibs.fess/fess-webapp-example) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](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. +A minimal, copy-from template for building a [Fess](https://fess.codelibs.org/) **WebApp plugin**. -## Overview +It shows the one thing every WebApp plugin needs to get right: how to **add** your +own components to Fess's dependency-injection (DI) container using the additive +`app++.xml` merge convention, **without overriding or copying any Fess core +component**. -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. +## What this plugin does -### Key Features +- Registers a single new component, `exampleHelper` + ([`ExampleHelper`](src/main/java/org/codelibs/fess/webapp/example/helper/ExampleHelper.java)), + via [`app++.xml`](src/main/resources/app++.xml). +- `ExampleHelper` has one small, real method, `getPluginLabel()`, which reads the + running Fess version from the core `SystemHelper` (looked up through + `ComponentUtil`) and returns a label such as `fess-webapp-example (Fess 15.7.0)`. -- **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 +That is deliberately tiny. The value of this repository is the *wiring*, which you +can copy and replace with your own helper, action, or service. -## Supported UI Components +## How the additive merge works -The plugin registers custom JSP templates for the following components: +Fess assembles its DI container from many small LastaDi XML files. A plugin can +contribute to that container by shipping XML files on the classpath whose names +follow LastaDi's merge conventions: -### 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 +| File name pattern | Effect | +| --- | --- | +| `app++.xml` | **Adds** components to the `app` namespace (merge). Used here. | +| `fess_query+.xml` | **Overrides** the core component named ``. | +| `fess_api++.xml` | **Adds** components to the `fess_api` namespace (merge). | -### Navigation & Layout -- `header.jsp` - Page header -- `footer.jsp` - Page footer - -### 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 - -### From Maven Repository - -The plugin is available on Maven Central: +The `++` suffix means "merge into this namespace". Because `exampleHelper` does +not exist in Fess core, nothing is overridden — the component is simply added. +This is the recommended way to extend Fess: add new components rather than +replacing core singletons. ```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 +## The `Fess-WebAppJar` manifest -### Building from Source +A WebApp plugin JAR must declare itself with the manifest entry +`Fess-WebAppJar: true` so Fess loads its classes and DI XML into the web +application. This is set in [`pom.xml`](pom.xml) via the `maven-jar-plugin`: -```bash -git clone https://github.com/codelibs/fess-webapp-example.git -cd fess-webapp-example -mvn clean package +```xml + + maven-jar-plugin + + + + true + + + + ``` -The compiled JAR will be available in the `target/` directory. +## Requirements -## Development +- Java 21 or later +- Maven 3.8 or later +- Fess 15.7 or later -### Project Structure +## Project structure ``` src/ ├── main/ │ ├── java/ -│ │ └── org/codelibs/fess/plugin/webapp/helper/ -│ │ └── CustomSystemHelper.java +│ │ └── org/codelibs/fess/webapp/example/helper/ +│ │ └── ExampleHelper.java │ └── resources/ -│ └── fess+systemHelper.xml +│ └── app++.xml └── test/ ├── java/ - │ └── org/codelibs/fess/plugin/webapp/helper/ - │ └── CustomSystemHelperTest.java + │ └── org/codelibs/fess/webapp/example/ + │ ├── UnitWebappTestCase.java + │ └── helper/ + │ └── ExampleHelperTest.java └── resources/ └── test_app.xml ``` -### Core Components - -#### CustomSystemHelper +All code lives under the single package root `org.codelibs.fess.webapp.example`. -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 +## Building and testing ```bash -# Compile the project +# Compile mvn clean compile # Run tests mvn test -# Create package +# Build the plugin JAR (lands in target/) mvn clean package -# Format code +# Format code and license headers before committing mvn formatter:format +mvn license:format +``` -# Check license headers -mvn license:check +The test ([`ExampleHelperTest`](src/test/java/org/codelibs/fess/webapp/example/helper/ExampleHelperTest.java)) +loads [`test_app.xml`](src/test/resources/test_app.xml), which ``s the +plugin's `app++.xml`, then retrieves `exampleHelper` from the DI container exactly +as Fess does at runtime. That proves the `app++.xml` wiring is correct. -# Generate documentation -mvn javadoc:javadoc -``` +## Installation -### Creating Custom Templates +### Building from source -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` +```bash +git clone https://github.com/codelibs/fess-webapp-example.git +cd fess-webapp-example +mvn clean package +``` -## Configuration +### Installing into Fess -The plugin uses LastaDi dependency injection framework. Template mappings are configured in `fess+systemHelper.xml`: +Place the built JAR in Fess's plugin directory, or install it from the admin UI. +See the [Plugin Installation Guide](https://fess.codelibs.org/15.7/admin/plugin-guide.html). -```xml - - - "index" - "index.jsp" - - - -``` +## How to extend it + +1. Add your own class (a helper, action, service, etc.) under + `org.codelibs.fess.webapp.example`. +2. Register it in `app++.xml` with a unique component name that does **not** + collide with a Fess core component (unless you intend to override one). +3. Use `@PostConstruct` for initialization and `ComponentUtil.getXxx()` / + `@Resource` to reuse core components instead of copying them. +4. Add a test that retrieves your component from the DI container and asserts its + behavior. ## Contributing @@ -169,25 +151,11 @@ The plugin uses LastaDi dependency injection framework. Template mappings are co 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 +## Related projects - [Fess](https://github.com/codelibs/fess) - The main Fess search server - [LastaFlute](https://github.com/lastaflute/lastaflute) - Web framework used by Fess @@ -195,4 +163,4 @@ This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENS --- -**CodeLibs Project** - https://www.codelibs.org/ \ No newline at end of file +**CodeLibs Project** - https://www.codelibs.org/ diff --git a/pom.xml b/pom.xml index ad8c389..14189e0 100644 --- a/pom.xml +++ b/pom.xml @@ -51,8 +51,8 @@ - snapshots.oss.sonatype.org - https://oss.sonatype.org/content/repositories/snapshots + snapshots.central.sonatype.com + https://central.sonatype.com/repository/maven-snapshots/ false 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 deleted file mode 100644 index 1174776..0000000 --- a/src/main/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelper.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2012-2025 CodeLibs Project and the Others. - * - * Licensed 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.codelibs.fess.plugin.webapp.helper; - -import java.nio.file.Path; - -import org.apache.logging.log4j.LogManager; -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 - protected void parseProjectProperties(final Path propPath) { - try { - super.parseProjectProperties(propPath); - } catch (final Exception e) { - logger.warn("Cannot parse project.properties.", e); - } - System.setProperty("fess.webapp.plugin", "true"); - } -} diff --git a/src/main/java/org/codelibs/fess/webapp/example/helper/ExampleHelper.java b/src/main/java/org/codelibs/fess/webapp/example/helper/ExampleHelper.java new file mode 100644 index 0000000..85a3a66 --- /dev/null +++ b/src/main/java/org/codelibs/fess/webapp/example/helper/ExampleHelper.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.webapp.example.helper; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.codelibs.fess.helper.SystemHelper; +import org.codelibs.fess.util.ComponentUtil; + +import jakarta.annotation.PostConstruct; + +/** + * A minimal example helper that demonstrates how a Fess WebApp plugin can add a + * brand-new component to Fess's DI container via the additive {@code app++.xml} + * merge convention. + * + *

+ * This class intentionally does something small and easy to read: it builds a + * short label that identifies the plugin. Use it as a starting point for your + * own helper, action, or service. + *

+ * + *

+ * Note how it follows the standard Fess idioms: + *

+ *
    + *
  • {@code @PostConstruct} for one-time initialization after DI wiring.
  • + *
  • {@link ComponentUtil} to look up other Fess components (here the core + * {@link SystemHelper}) instead of overriding or copying them.
  • + *
+ */ +public class ExampleHelper { + + private static final Logger logger = LogManager.getLogger(ExampleHelper.class); + + /** Display name of this plugin. */ + protected String pluginName = "fess-webapp-example"; + + /** + * Default constructor. + */ + public ExampleHelper() { + // nothing + } + + /** + * Initializes this helper once after the DI container has wired it up. + */ + @PostConstruct + public void init() { + if (logger.isDebugEnabled()) { + logger.debug("Initialized {}.", getClass().getSimpleName()); + } + } + + /** + * Returns a short label identifying this plugin and the running Fess + * version. The version is read from the core {@link SystemHelper} component + * via {@link ComponentUtil}, demonstrating how a plugin can reuse core + * components without overriding them. + * + * @return a label such as {@code "fess-webapp-example (Fess 15.7.0)"} + */ + public String getPluginLabel() { + final SystemHelper systemHelper = ComponentUtil.getSystemHelper(); + final String version = systemHelper != null ? systemHelper.getProductVersion() : "unknown"; + return pluginName + " (Fess " + version + ")"; + } + + /** + * Returns the configured plugin name. + * + * @return the plugin name + */ + public String getPluginName() { + return pluginName; + } + + /** + * Sets the plugin name. Exposed so it can be configured from the DI XML if + * desired. + * + * @param pluginName the plugin name to use + */ + public void setPluginName(final String pluginName) { + this.pluginName = pluginName; + } +} diff --git a/src/main/resources/app++.xml b/src/main/resources/app++.xml new file mode 100644 index 0000000..3d6eef9 --- /dev/null +++ b/src/main/resources/app++.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/src/main/resources/fess+systemHelper.xml b/src/main/resources/fess+systemHelper.xml deleted file mode 100644 index f18bfb4..0000000 --- a/src/main/resources/fess+systemHelper.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - "index" - "index.jsp" - - - "header" - "header.jsp" - - - "footer" - "footer.jsp" - - - "search" - "search.jsp" - - - "searchResults" - "searchResults.jsp" - - - "searchNoResult" - "searchNoResult.jsp" - - - "searchOptions" - "searchOptions.jsp" - - - "advance" - "advance.jsp" - - - "help" - "help.jsp" - - - "error" - "error/error.jsp" - - - "errorNotFound" - "error/notFound.jsp" - - - "errorSystem" - "error/system.jsp" - - - "errorRedirect" - "error/redirect.jsp" - - - "errorBadRequest" - "error/badRequest.jsp" - - - "cache" - "cache.hbs" - - - "login" - "login/index.jsp" - - - "profile" - "profile/index.jsp" - - - 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 deleted file mode 100644 index 5faf50b..0000000 --- a/src/test/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelperTest.java +++ /dev/null @@ -1,429 +0,0 @@ -/* - * Copyright 2012-2025 CodeLibs Project and the Others. - * - * Licensed 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.codelibs.fess.plugin.webapp.helper; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; - -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.codelibs.fess.webapp.example.UnitWebappTestCase; - -public class CustomSystemHelperTest extends UnitWebappTestCase { - - @Override - protected String prepareConfigFile() { - return "test_app.xml"; - } - - @Override - protected boolean isSuppressTestCaseTransaction() { - return true; - } - - @Override - public void setUp(TestInfo testInfo) throws Exception { - ComponentUtil.setFessConfig(new FessConfig.SimpleImpl() { - private static final long serialVersionUID = 1L; - - @Override - public String getAppValue() { - return ""; - } - - @Override - public String getPathEncoding() { - return "UTF-8"; - } - - @Override - public String[] getSupportedLanguagesAsArray() { - return new String[] { "ja" }; - } - - }); - super.setUp(testInfo); - } - - @Override - public void tearDown(TestInfo testInfo) throws Exception { - ComponentUtil.setFessConfig(null); - super.tearDown(testInfo); - } - - @Test - public void test_checkProperty() { - assertEquals("true", System.getProperty("fess.webapp.plugin")); - } - - @Test - 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")); - } - - @Test - public void test_parseProjectProperties_withNullPath() { - // Given - CustomSystemHelper helper = new CustomSystemHelper(); - - // When - helper.parseProjectProperties(null); - - // Then - assertEquals("true", System.getProperty("fess.webapp.plugin")); - } - - @Test - 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")); - } - - @Test - 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")); - } - - @Test - 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")); - } - - @Test - public void test_inheritance_extendsSystemHelper() { - // Given - CustomSystemHelper helper = new CustomSystemHelper(); - - // Then - assertTrue("CustomSystemHelper should extend SystemHelper", helper instanceof org.codelibs.fess.helper.SystemHelper); - } - - @Test - 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); - } - - @Test - 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")); - } - - @Test - 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); - } - - @Test - 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); - } - } - - @Test - public void test_constructor_initialization() { - // Given & When - CustomSystemHelper helper = new CustomSystemHelper(); - - // Then - assertNotNull("Helper instance should not be null", helper); - assertTrue("Helper should be instance of SystemHelper", helper instanceof org.codelibs.fess.helper.SystemHelper); - assertTrue("Helper should be instance of CustomSystemHelper", helper instanceof CustomSystemHelper); - } - - @Test - public void test_parseProjectProperties_overwritesExistingProperty() { - // Given - CustomSystemHelper helper = new CustomSystemHelper(); - System.setProperty("fess.webapp.plugin", "false"); - assertEquals("false", System.getProperty("fess.webapp.plugin")); - - // When - helper.parseProjectProperties(null); - - // Then - assertEquals("Property should be overwritten to true", "true", System.getProperty("fess.webapp.plugin")); - } - - @Test - public void test_parseProjectProperties_withAbsolutePath() { - // Given - CustomSystemHelper helper = new CustomSystemHelper(); - Path absolutePath = Paths.get("/tmp/test/project.properties").toAbsolutePath(); - - // When - helper.parseProjectProperties(absolutePath); - - // Then - assertEquals("true", System.getProperty("fess.webapp.plugin")); - } - - @Test - public void test_parseProjectProperties_withRelativePath() { - // Given - CustomSystemHelper helper = new CustomSystemHelper(); - Path relativePath = Paths.get("./project.properties"); - - // When - helper.parseProjectProperties(relativePath); - - // Then - assertEquals("true", System.getProperty("fess.webapp.plugin")); - } - - @Test - public void test_parseProjectProperties_withSpecialCharactersInPath() { - // Given - CustomSystemHelper helper = new CustomSystemHelper(); - Path pathWithSpaces = Paths.get("path with spaces/project.properties"); - Path pathWithUnicode = Paths.get("パス/project.properties"); - - // When - helper.parseProjectProperties(pathWithSpaces); - String resultAfterSpaces = System.getProperty("fess.webapp.plugin"); - - helper.parseProjectProperties(pathWithUnicode); - String resultAfterUnicode = System.getProperty("fess.webapp.plugin"); - - // Then - assertEquals("true", resultAfterSpaces); - assertEquals("true", resultAfterUnicode); - } - - @Test - public void test_multipleInstances_independence() { - // Given - CustomSystemHelper helper1 = new CustomSystemHelper(); - CustomSystemHelper helper2 = new CustomSystemHelper(); - CustomSystemHelper helper3 = new CustomSystemHelper(); - - // When - helper1.parseProjectProperties(Paths.get("path1")); - helper2.parseProjectProperties(Paths.get("path2")); - helper3.parseProjectProperties(null); - - // Then - assertEquals("true", System.getProperty("fess.webapp.plugin")); - assertNotSame("Instances should be different objects", helper1, helper2); - assertNotSame("Instances should be different objects", helper2, helper3); - } - - @Test - public void test_parseProjectProperties_consistencyAcrossInstances() { - // Given - System.clearProperty("fess.webapp.plugin"); - CustomSystemHelper helper1 = new CustomSystemHelper(); - CustomSystemHelper helper2 = new CustomSystemHelper(); - - // When - helper1.parseProjectProperties(Paths.get("test1")); - String valueAfterHelper1 = System.getProperty("fess.webapp.plugin"); - - helper2.parseProjectProperties(Paths.get("test2")); - String valueAfterHelper2 = System.getProperty("fess.webapp.plugin"); - - // Then - assertEquals("true", valueAfterHelper1); - assertEquals("true", valueAfterHelper2); - assertEquals("Values should be consistent", valueAfterHelper1, valueAfterHelper2); - } - - @Test - public void test_parseProjectProperties_withDifferentPathTypes() { - // Given - CustomSystemHelper helper = new CustomSystemHelper(); - Path[] testPaths = { null, Paths.get(""), Paths.get("relative/path"), Paths.get("/absolute/path"), Paths.get("../parent/path"), - Paths.get("./current/path") }; - - // When & Then - for (Path testPath : testPaths) { - System.clearProperty("fess.webapp.plugin"); - helper.parseProjectProperties(testPath); - assertEquals("Property should be set for all path types", "true", System.getProperty("fess.webapp.plugin")); - } - } - - @Test - public void test_parseProjectProperties_propertyValueIsExactlyTrue() { - // Given - CustomSystemHelper helper = new CustomSystemHelper(); - System.clearProperty("fess.webapp.plugin"); - - // When - helper.parseProjectProperties(null); - String propertyValue = System.getProperty("fess.webapp.plugin"); - - // Then - assertNotNull("Property value should not be null", propertyValue); - assertEquals("Property value should be exactly 'true'", "true", propertyValue); - assertTrue("Property value should equal 'true' using equals", "true".equals(propertyValue)); - assertFalse("Property value should not be 'false'", "false".equals(propertyValue)); - assertFalse("Property value should not be empty", propertyValue.isEmpty()); - } - - @Test - public void test_parseProjectProperties_idempotency() { - // Given - CustomSystemHelper helper = new CustomSystemHelper(); - System.clearProperty("fess.webapp.plugin"); - - // When - helper.parseProjectProperties(null); - String firstValue = System.getProperty("fess.webapp.plugin"); - - helper.parseProjectProperties(null); - String secondValue = System.getProperty("fess.webapp.plugin"); - - helper.parseProjectProperties(Paths.get("different/path")); - String thirdValue = System.getProperty("fess.webapp.plugin"); - - // Then - assertEquals("true", firstValue); - assertEquals("true", secondValue); - assertEquals("true", thirdValue); - assertEquals("Multiple calls should produce same result (idempotent)", firstValue, secondValue); - assertEquals("Multiple calls should produce same result (idempotent)", secondValue, thirdValue); - } - - @Test - public void test_parseProjectProperties_doesNotThrowException() { - // Given - CustomSystemHelper helper = new CustomSystemHelper(); - Path[] problematicPaths = { null, Paths.get(""), Paths.get("/"), Paths.get("non/existent/path"), Paths.get("\\invalid\\path"), - Paths.get("special!@#$%/path") }; - - // When & Then - for (Path testPath : problematicPaths) { - try { - helper.parseProjectProperties(testPath); - // No exception should be thrown - assertTrue("Method should complete without exception", true); - } catch (Exception e) { - fail("Should not throw exception for path: " + testPath + ", but got: " + e.getMessage()); - } - } - } - - @Test - public void test_parseProjectProperties_stressTest() { - // Given - CustomSystemHelper helper = new CustomSystemHelper(); - final int iterations = 100; - - // When - long startTime = System.currentTimeMillis(); - for (int i = 0; i < iterations; i++) { - helper.parseProjectProperties(Paths.get("test/path/" + i)); - } - long endTime = System.currentTimeMillis(); - - // Then - assertEquals("Property should still be set after stress test", "true", System.getProperty("fess.webapp.plugin")); - long duration = endTime - startTime; - assertTrue("Stress test should complete in reasonable time (< 5 seconds)", duration < 5000); - } -} diff --git a/src/test/java/org/codelibs/fess/webapp/example/helper/ExampleHelperTest.java b/src/test/java/org/codelibs/fess/webapp/example/helper/ExampleHelperTest.java new file mode 100644 index 0000000..4c0d951 --- /dev/null +++ b/src/test/java/org/codelibs/fess/webapp/example/helper/ExampleHelperTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.webapp.example.helper; + +import org.codelibs.fess.helper.SystemHelper; +import org.codelibs.fess.util.ComponentUtil; +import org.codelibs.fess.webapp.example.UnitWebappTestCase; +import org.junit.jupiter.api.Test; + +/** + * Verifies that {@link ExampleHelper} is registered through the additive + * {@code app++.xml} merge convention and that its method works. + * + *

+ * The test retrieves the component from the DI container exactly as Fess does + * at runtime, which proves the {@code app++.xml} wiring is correct. + *

+ */ +public class ExampleHelperTest extends UnitWebappTestCase { + + @Test + public void test_exampleHelper_isRegisteredViaDi() { + // app++.xml registered "exampleHelper"; look it up by name. + final ExampleHelper exampleHelper = ComponentUtil.getComponent("exampleHelper"); + assertNotNull("exampleHelper should be registered via app++.xml", exampleHelper); + } + + @Test + public void test_getPluginName() { + final ExampleHelper exampleHelper = ComponentUtil.getComponent("exampleHelper"); + assertEquals("default plugin name", "fess-webapp-example", exampleHelper.getPluginName()); + } + + @Test + public void test_getPluginLabel_includesVersion() { + // Register a core SystemHelper so the helper can read the product version. + final SystemHelper systemHelper = new SystemHelper() { + @Override + public String getProductVersion() { + return "15.7.0"; + } + }; + ComponentUtil.register(systemHelper, "systemHelper"); + + final ExampleHelper exampleHelper = ComponentUtil.getComponent("exampleHelper"); + assertEquals("label combines plugin name and Fess version", "fess-webapp-example (Fess 15.7.0)", exampleHelper.getPluginLabel()); + } +} diff --git a/src/test/resources/test_app.xml b/src/test/resources/test_app.xml index 4cd540d..9979024 100644 --- a/src/test/resources/test_app.xml +++ b/src/test/resources/test_app.xml @@ -4,6 +4,7 @@ - - + + From 5a7d903cab5b5da05bcc1681d89a690555805be4 Mon Sep 17 00:00:00 2001 From: Shinsuke Sugaya Date: Fri, 26 Jun 2026 09:33:00 +0900 Subject: [PATCH 2/2] Restore CustomSystemHelper as a core-component override example Keep ExampleHelper (added via the additive app++.xml merge) and additionally restore CustomSystemHelper so the example demonstrates both ways a WebApp plugin extends Fess's DI container: - Add a new component: ExampleHelper via app++.xml - Override a core component: CustomSystemHelper (extends SystemHelper) via the fess+systemHelper.xml per-component redefine Details: - Move CustomSystemHelper into the unified package org.codelibs.fess.webapp.example.helper - Sync the design-JSP (addDesignJspFileName) list in fess+systemHelper.xml to current Fess core (19 entries) so overriding systemHelper does not drop core design mappings (chat/busy/newpassword) - Add a concise CustomSystemHelperTest (loads test_systemhelper.xml) and keep ExampleHelperTest - Rewrite README to document both patterns and the LastaDi merge/redefine conventions - Align example version references to 15.8 --- README.md | 132 ++++++++++++------ .../example/helper/CustomSystemHelper.java | 76 ++++++++++ .../webapp/example/helper/ExampleHelper.java | 2 +- src/main/resources/fess+systemHelper.xml | 104 ++++++++++++++ .../helper/CustomSystemHelperTest.java | 103 ++++++++++++++ .../example/helper/ExampleHelperTest.java | 4 +- src/test/resources/test_systemhelper.xml | 11 ++ 7 files changed, 390 insertions(+), 42 deletions(-) create mode 100644 src/main/java/org/codelibs/fess/webapp/example/helper/CustomSystemHelper.java create mode 100644 src/main/resources/fess+systemHelper.xml create mode 100644 src/test/java/org/codelibs/fess/webapp/example/helper/CustomSystemHelperTest.java create mode 100644 src/test/resources/test_systemhelper.xml diff --git a/README.md b/README.md index d32bdc5..2889e3f 100644 --- a/README.md +++ b/README.md @@ -6,39 +6,43 @@ A minimal, copy-from template for building a [Fess](https://fess.codelibs.org/) **WebApp plugin**. -It shows the one thing every WebApp plugin needs to get right: how to **add** your -own components to Fess's dependency-injection (DI) container using the additive -`app++.xml` merge convention, **without overriding or copying any Fess core -component**. +It shows the two ways a WebApp plugin extends Fess's dependency-injection (DI) +container, side by side: -## What this plugin does +1. **Add a new component** — the recommended default. Register your own + helper/action/service without touching Fess core. +2. **Override a core component** — replace a built-in Fess component with + your own subclass when you genuinely need to change core behavior. -- Registers a single new component, `exampleHelper` - ([`ExampleHelper`](src/main/java/org/codelibs/fess/webapp/example/helper/ExampleHelper.java)), - via [`app++.xml`](src/main/resources/app++.xml). -- `ExampleHelper` has one small, real method, `getPluginLabel()`, which reads the - running Fess version from the core `SystemHelper` (looked up through - `ComponentUtil`) and returns a label such as `fess-webapp-example (Fess 15.7.0)`. +Both are tiny on purpose. The value of this repository is the *wiring* and the +*conventions*, which you can copy and replace with your own classes. -That is deliberately tiny. The value of this repository is the *wiring*, which you -can copy and replace with your own helper, action, or service. +## How Fess assembles plugin DI -## How the additive merge works - -Fess assembles its DI container from many small LastaDi XML files. A plugin can -contribute to that container by shipping XML files on the classpath whose names -follow LastaDi's merge conventions: +Fess builds its DI container from many small LastaDi XML files. A plugin +contributes by shipping XML files on the classpath whose **file names** follow +LastaDi's redefine conventions: | File name pattern | Effect | | --- | --- | -| `app++.xml` | **Adds** components to the `app` namespace (merge). Used here. | -| `fess_query+.xml` | **Overrides** the core component named ``. | -| `fess_api++.xml` | **Adds** components to the `fess_api` namespace (merge). | +| `++.xml` (e.g. `app++.xml`) | **Adds** new components into the container built from `.xml` (additive merge). Nothing is overridden. | +| `+.xml` (e.g. `fess+systemHelper.xml`) | **Replaces** the single component named `` that `.xml` declares (per-component redefine). | + +Two things are easy to get wrong: + +- The **prefix must be the dicon that declares the component.** `systemHelper` is + declared in Fess core's `fess.xml`, so the override file must be + `fess+systemHelper.xml` — not `app+systemHelper.xml`. +- A redefine (`+`) replaces the **entire** component definition, including its + `postConstruct` calls. Registering the same component `name` twice in one + container is an *ambiguity error*, not an override — the `+` convention is + exactly what lets you replace a core singleton cleanly. + +## Pattern 1: Add a new component (`app++.xml` + `ExampleHelper`) -The `++` suffix means "merge into this namespace". Because `exampleHelper` does -not exist in Fess core, nothing is overridden — the component is simply added. -This is the recommended way to extend Fess: add new components rather than -replacing core singletons. +[`app++.xml`](src/main/resources/app++.xml) merges a single new component, +`exampleHelper`, into the `app` container. Because `exampleHelper` does not exist +in Fess core, nothing is overridden. ```xml @@ -48,11 +52,50 @@ replacing core singletons. ``` +[`ExampleHelper`](src/main/java/org/codelibs/fess/webapp/example/helper/ExampleHelper.java) +has one small, real method, `getPluginLabel()`, which reads the running Fess +version from the core `SystemHelper` (looked up through `ComponentUtil`) and +returns a label such as `fess-webapp-example (Fess 15.8)`. It follows the +standard Fess idioms: `@PostConstruct` for initialization and `ComponentUtil` to +*reuse* core components instead of copying or overriding them. + +This is the pattern you should reach for first. + +## Pattern 2: Override a core component (`fess+systemHelper.xml` + `CustomSystemHelper`) + +[`CustomSystemHelper`](src/main/java/org/codelibs/fess/webapp/example/helper/CustomSystemHelper.java) +extends Fess's `SystemHelper` and overrides `parseProjectProperties()` to tolerate +a parse failure and set a `fess.webapp.plugin` marker property. +[`fess+systemHelper.xml`](src/main/resources/fess+systemHelper.xml) re-registers +`systemHelper` with this subclass: + +```xml + + + + "index" + "index.jsp" + + + +``` + +> **Maintenance cost (read before overriding):** because a redefine replaces the +> *whole* definition, the override must repeat **every** `postConstruct` the core +> `systemHelper` performs — the full set of design-JSP name mappings. These +> are copied verbatim from Fess core's `fess.xml`, and the referenced `*.jsp` +> files are provided by Fess itself (this plugin ships none of them). You must +> keep that list in sync with each Fess release, or design pages (e.g. +> `chat` / `busy` / `newpassword`) will stop resolving. This is exactly why +> Pattern 1 is preferred whenever you do not truly need to replace core behavior. + ## The `Fess-WebAppJar` manifest A WebApp plugin JAR must declare itself with the manifest entry -`Fess-WebAppJar: true` so Fess loads its classes and DI XML into the web -application. This is set in [`pom.xml`](pom.xml) via the `maven-jar-plugin`: +`Fess-WebAppJar: true` so Fess mounts its classes and DI XML into the web +application's classloader. This is set in [`pom.xml`](pom.xml) via the +`maven-jar-plugin`: ```xml @@ -71,7 +114,7 @@ application. This is set in [`pom.xml`](pom.xml) via the `maven-jar-plugin`: - Java 21 or later - Maven 3.8 or later -- Fess 15.7 or later +- Fess 15.8 or later ## Project structure @@ -80,17 +123,21 @@ src/ ├── main/ │ ├── java/ │ │ └── org/codelibs/fess/webapp/example/helper/ -│ │ └── ExampleHelper.java +│ │ ├── ExampleHelper.java # Pattern 1: a brand-new component +│ │ └── CustomSystemHelper.java # Pattern 2: a core-component override │ └── resources/ -│ └── app++.xml +│ ├── app++.xml # Pattern 1: additive merge +│ └── fess+systemHelper.xml # Pattern 2: per-component redefine └── test/ ├── java/ │ └── org/codelibs/fess/webapp/example/ │ ├── UnitWebappTestCase.java │ └── helper/ - │ └── ExampleHelperTest.java + │ ├── ExampleHelperTest.java + │ └── CustomSystemHelperTest.java └── resources/ - └── test_app.xml + ├── test_app.xml # loads app++.xml for Pattern 1 + └── test_systemhelper.xml # loads fess+systemHelper.xml for Pattern 2 ``` All code lives under the single package root `org.codelibs.fess.webapp.example`. @@ -112,10 +159,16 @@ mvn formatter:format mvn license:format ``` -The test ([`ExampleHelperTest`](src/test/java/org/codelibs/fess/webapp/example/helper/ExampleHelperTest.java)) -loads [`test_app.xml`](src/test/resources/test_app.xml), which ``s the -plugin's `app++.xml`, then retrieves `exampleHelper` from the DI container exactly -as Fess does at runtime. That proves the `app++.xml` wiring is correct. +Each test retrieves its component from the DI container exactly as Fess does at +runtime, which proves the wiring is correct: + +- [`ExampleHelperTest`](src/test/java/org/codelibs/fess/webapp/example/helper/ExampleHelperTest.java) + loads [`test_app.xml`](src/test/resources/test_app.xml) (which ``s + `app++.xml`) and looks up `exampleHelper`. +- [`CustomSystemHelperTest`](src/test/java/org/codelibs/fess/webapp/example/helper/CustomSystemHelperTest.java) + loads [`test_systemhelper.xml`](src/test/resources/test_systemhelper.xml) (which + ``s `fess+systemHelper.xml`) and verifies `systemHelper` resolves to the + overriding `CustomSystemHelper`. ## Installation @@ -130,14 +183,15 @@ mvn clean package ### Installing into Fess Place the built JAR in Fess's plugin directory, or install it from the admin UI. -See the [Plugin Installation Guide](https://fess.codelibs.org/15.7/admin/plugin-guide.html). +See the [Plugin Installation Guide](https://fess.codelibs.org/15.8/admin/plugin-guide.html). ## How to extend it 1. Add your own class (a helper, action, service, etc.) under `org.codelibs.fess.webapp.example`. -2. Register it in `app++.xml` with a unique component name that does **not** - collide with a Fess core component (unless you intend to override one). +2. To **add** it, register it in `app++.xml` with a unique component name that + does not collide with a Fess core component. To **override** a core component, + ship a `+.xml` file and subclass the original. 3. Use `@PostConstruct` for initialization and `ComponentUtil.getXxx()` / `@Resource` to reuse core components instead of copying them. 4. Add a test that retrieves your component from the DI container and asserts its diff --git a/src/main/java/org/codelibs/fess/webapp/example/helper/CustomSystemHelper.java b/src/main/java/org/codelibs/fess/webapp/example/helper/CustomSystemHelper.java new file mode 100644 index 0000000..6424587 --- /dev/null +++ b/src/main/java/org/codelibs/fess/webapp/example/helper/CustomSystemHelper.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.webapp.example.helper; + +import java.nio.file.Path; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.codelibs.fess.helper.SystemHelper; + +/** + * An example helper that demonstrates how a Fess WebApp plugin can override + * a core Fess component by subclassing it and re-registering it under the same + * component name via the {@code fess+systemHelper.xml} redefine convention. + * + *

+ * Fess declares the core {@code systemHelper} component in its {@code fess.xml}. + * Shipping a classpath resource named {@code fess+.xml} (here + * {@code fess+systemHelper.xml}) tells LastaDi to register THIS class in place + * of the core {@link SystemHelper}. Because a redefine REPLACES the whole + * component definition, the override XML must repeat every {@code postConstruct} + * the core definition performs (the design-JSP name mappings) — see + * {@code fess+systemHelper.xml} for the details and the maintenance cost this + * implies. + *

+ * + *

+ * The only behavior this subclass adds is in {@link #parseProjectProperties}: it + * tolerates a parse failure instead of aborting startup, and records a marker + * system property so other code can detect that the plugin is active. Replace + * this with your own override logic. + *

+ * + * @see ExampleHelper for the complementary "add a brand-new component" example. + */ +public class CustomSystemHelper extends SystemHelper { + + private static final Logger logger = LogManager.getLogger(CustomSystemHelper.class); + + /** + * Default constructor. + */ + public CustomSystemHelper() { + super(); + } + + /** + * Overrides the core parsing of {@code project.properties} so that Fess keeps + * starting even when the file cannot be parsed, and marks this plugin as + * active via the {@code fess.webapp.plugin} system property. + * + * @param propPath the path to {@code project.properties} + */ + @Override + protected void parseProjectProperties(final Path propPath) { + try { + super.parseProjectProperties(propPath); + } catch (final Exception e) { + logger.warn("Cannot parse project.properties.", e); + } + System.setProperty("fess.webapp.plugin", "true"); + } +} diff --git a/src/main/java/org/codelibs/fess/webapp/example/helper/ExampleHelper.java b/src/main/java/org/codelibs/fess/webapp/example/helper/ExampleHelper.java index 85a3a66..59f6b16 100644 --- a/src/main/java/org/codelibs/fess/webapp/example/helper/ExampleHelper.java +++ b/src/main/java/org/codelibs/fess/webapp/example/helper/ExampleHelper.java @@ -72,7 +72,7 @@ public void init() { * via {@link ComponentUtil}, demonstrating how a plugin can reuse core * components without overriding them. * - * @return a label such as {@code "fess-webapp-example (Fess 15.7.0)"} + * @return a label such as {@code "fess-webapp-example (Fess 15.8)"} */ public String getPluginLabel() { final SystemHelper systemHelper = ComponentUtil.getSystemHelper(); diff --git a/src/main/resources/fess+systemHelper.xml b/src/main/resources/fess+systemHelper.xml new file mode 100644 index 0000000..0b488cf --- /dev/null +++ b/src/main/resources/fess+systemHelper.xml @@ -0,0 +1,104 @@ + + + + + + + "index" + "index.jsp" + + + "header" + "header.jsp" + + + "footer" + "footer.jsp" + + + "search" + "search.jsp" + + + "searchResults" + "searchResults.jsp" + + + "searchNoResult" + "searchNoResult.jsp" + + + "searchOptions" + "searchOptions.jsp" + + + "advance" + "advance.jsp" + + + "help" + "help.jsp" + + + "error" + "error/error.jsp" + + + "errorNotFound" + "error/notFound.jsp" + + + "errorSystem" + "error/system.jsp" + + + "errorBadRequest" + "error/badRequest.jsp" + + + "errorBusy" + "error/busy.jsp" + + + "cache" + "cache.hbs" + + + "login" + "login/index.jsp" + + + "newpassword" + "login/newpassword.jsp" + + + "profile" + "profile/index.jsp" + + + "chat" + "chat/chat.jsp" + + + diff --git a/src/test/java/org/codelibs/fess/webapp/example/helper/CustomSystemHelperTest.java b/src/test/java/org/codelibs/fess/webapp/example/helper/CustomSystemHelperTest.java new file mode 100644 index 0000000..1e07d5e --- /dev/null +++ b/src/test/java/org/codelibs/fess/webapp/example/helper/CustomSystemHelperTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.webapp.example.helper; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.codelibs.fess.helper.SystemHelper; +import org.codelibs.fess.mylasta.direction.FessConfig; +import org.codelibs.fess.util.ComponentUtil; +import org.codelibs.fess.webapp.example.UnitWebappTestCase; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +/** + * Verifies that {@link CustomSystemHelper} replaces the core {@code systemHelper} + * component through the {@code fess+systemHelper.xml} redefine convention, and + * that its {@link CustomSystemHelper#parseProjectProperties} override is resilient + * to a parse failure. + * + *

+ * This test loads {@code test_systemhelper.xml}, which {@code }s the + * plugin's {@code fess+systemHelper.xml}, then retrieves {@code systemHelper} from + * the DI container the same way Fess looks it up. This confirms the override XML + * is valid and registers {@link CustomSystemHelper} in place of the core + * {@code systemHelper}. + *

+ */ +public class CustomSystemHelperTest extends UnitWebappTestCase { + + @Override + protected String prepareConfigFile() { + return "test_systemhelper.xml"; + } + + @Override + public void setUp(final TestInfo testInfo) throws Exception { + // SystemHelper#init() reads FessConfig, so provide a minimal one before + // the container resolves "systemHelper". + ComponentUtil.setFessConfig(new FessConfig.SimpleImpl() { + private static final long serialVersionUID = 1L; + + // Returning blank keeps SystemHelper#updateSystemProperties() from + // reading the "systemProperties" component, which this minimal test + // container does not register. + @Override + public String getAppValue() { + return ""; + } + + @Override + public String getPathEncoding() { + return "UTF-8"; + } + + @Override + public String[] getSupportedLanguagesAsArray() { + return new String[] { "ja" }; + } + }); + super.setUp(testInfo); + } + + @Override + public void tearDown(final TestInfo testInfo) throws Exception { + ComponentUtil.setFessConfig(null); + super.tearDown(testInfo); + } + + @Test + public void test_systemHelper_isOverridden() { + // fess+systemHelper.xml redefined "systemHelper" as CustomSystemHelper. + final SystemHelper systemHelper = ComponentUtil.getSystemHelper(); + assertNotNull("systemHelper should be registered", systemHelper); + assertTrue("systemHelper should be the overridden CustomSystemHelper", systemHelper instanceof CustomSystemHelper); + } + + @Test + public void test_parseProjectProperties_setsPluginFlagOnFailure() { + // A non-existent path makes super.parseProjectProperties fail; the + // override must swallow the error and still set the marker property. + System.clearProperty("fess.webapp.plugin"); + final CustomSystemHelper helper = new CustomSystemHelper(); + final Path missing = Paths.get("no/such/project.properties"); + + helper.parseProjectProperties(missing); + + assertEquals("plugin marker should be set even on parse failure", "true", System.getProperty("fess.webapp.plugin")); + } +} diff --git a/src/test/java/org/codelibs/fess/webapp/example/helper/ExampleHelperTest.java b/src/test/java/org/codelibs/fess/webapp/example/helper/ExampleHelperTest.java index 4c0d951..642ee45 100644 --- a/src/test/java/org/codelibs/fess/webapp/example/helper/ExampleHelperTest.java +++ b/src/test/java/org/codelibs/fess/webapp/example/helper/ExampleHelperTest.java @@ -50,12 +50,12 @@ public void test_getPluginLabel_includesVersion() { final SystemHelper systemHelper = new SystemHelper() { @Override public String getProductVersion() { - return "15.7.0"; + return "15.8"; } }; ComponentUtil.register(systemHelper, "systemHelper"); final ExampleHelper exampleHelper = ComponentUtil.getComponent("exampleHelper"); - assertEquals("label combines plugin name and Fess version", "fess-webapp-example (Fess 15.7.0)", exampleHelper.getPluginLabel()); + assertEquals("label combines plugin name and Fess version", "fess-webapp-example (Fess 15.8)", exampleHelper.getPluginLabel()); } } diff --git a/src/test/resources/test_systemhelper.xml b/src/test/resources/test_systemhelper.xml new file mode 100644 index 0000000..0f3db86 --- /dev/null +++ b/src/test/resources/test_systemhelper.xml @@ -0,0 +1,11 @@ + + + + + + + +