diff --git a/README.md b/README.md index 7e7e751..2889e3f 100644 --- a/README.md +++ b/README.md @@ -4,163 +4,199 @@ [![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 two ways a WebApp plugin extends Fess's dependency-injection (DI) +container, side by side: -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. +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. -### Key Features +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. -- **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 +## How Fess assembles plugin DI -## Supported UI Components +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: -The plugin registers custom JSP templates for the following components: +| File name pattern | Effect | +| --- | --- | +| `++.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). | -### 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 +Two things are easy to get wrong: -### Navigation & Layout -- `header.jsp` - Page header -- `footer.jsp` - Page footer +- 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. -### 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 +## Pattern 1: Add a new component (`app++.xml` + `ExampleHelper`) -### User Interface -- `login/index.jsp` - Login page -- `profile/index.jsp` - User profile page +[`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. -### Cache Display -- `cache.hbs` - Cache display template (Handlebars) - -## Requirements +```xml + + + + +``` -- Java 21 or later -- Maven 3.6 or later -- Fess 15.0 or later +[`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. -## Installation +This is the pattern you should reach for first. -### From Maven Repository +## Pattern 2: Override a core component (`fess+systemHelper.xml` + `CustomSystemHelper`) -The plugin is available on Maven Central: +[`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 - - org.codelibs.fess - fess-webapp-example - 15.0.0 - + + + + "index" + "index.jsp" + + + ``` -### Manual Installation +> **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. -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 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`: -```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.8 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 # Pattern 1: a brand-new component +│ │ └── CustomSystemHelper.java # Pattern 2: a core-component override │ └── resources/ -│ └── fess+systemHelper.xml +│ ├── app++.xml # Pattern 1: additive merge +│ └── fess+systemHelper.xml # Pattern 2: per-component redefine └── test/ ├── java/ - │ └── org/codelibs/fess/plugin/webapp/helper/ - │ └── CustomSystemHelperTest.java + │ └── org/codelibs/fess/webapp/example/ + │ ├── UnitWebappTestCase.java + │ └── helper/ + │ ├── 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 ``` -### 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 +All code lives under the single package root `org.codelibs.fess.webapp.example`. -- **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 - -# Check license headers -mvn license:check - -# Generate documentation -mvn javadoc:javadoc +mvn license:format ``` -### Creating Custom Templates +Each test retrieves its component from the DI container exactly as Fess does at +runtime, which proves the wiring is correct: -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` +- [`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`. -## Configuration +## Installation -The plugin uses LastaDi dependency injection framework. Template mappings are configured in `fess+systemHelper.xml`: +### Building from source -```xml - - - "index" - "index.jsp" - - - +```bash +git clone https://github.com/codelibs/fess-webapp-example.git +cd fess-webapp-example +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.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. 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 + behavior. + ## Contributing 1. Fork the repository @@ -169,25 +205,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 +217,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/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 new file mode 100644 index 0000000..59f6b16 --- /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.8)"} + */ + 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 index f18bfb4..0b488cf 100644 --- a/src/main/resources/fess+systemHelper.xml +++ b/src/main/resources/fess+systemHelper.xml @@ -1,8 +1,29 @@ + - + "index" "index.jsp" @@ -51,14 +72,14 @@ "errorSystem" "error/system.jsp" - - "errorRedirect" - "error/redirect.jsp" - "errorBadRequest" "error/badRequest.jsp" + + "errorBusy" + "error/busy.jsp" + "cache" "cache.hbs" @@ -67,9 +88,17 @@ "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/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/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 new file mode 100644 index 0000000..642ee45 --- /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.8"; + } + }; + ComponentUtil.register(systemHelper, "systemHelper"); + + final ExampleHelper exampleHelper = ComponentUtil.getComponent("exampleHelper"); + assertEquals("label combines plugin name and Fess version", "fess-webapp-example (Fess 15.8)", 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 @@ - - + + 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 @@ + + + + + + + +