Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 188 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,198 @@
Example WebApp Plugin for Fess
# Fess WebApp Plugin Example

[![Java CI with Maven](https://github.com/codelibs/fess-webapp-example/actions/workflows/maven.yml/badge.svg)](https://github.com/codelibs/fess-webapp-example/actions/workflows/maven.yml)
==========================
[![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.

## Overview

This is a sample plugin for Fess webapp.
This plugin demonstrates how to extend Fess's web application layer by providing custom JSP templates for various UI components. It serves as a practical example for developers who want to create their own custom web interfaces for Fess search applications.

### Key Features

- **Custom JSP Templates**: Provides custom design templates for search pages, navigation, and error handling
- **System Helper Extension**: Extends Fess's `SystemHelper` class with enhanced error handling and logging
- **Component Registration**: Demonstrates dependency injection configuration using LastaDi framework
- **Comprehensive UI Coverage**: Includes templates for search interface, user management, and error pages

## Supported UI Components

The plugin registers custom JSP templates for the following components:

### Search Interface
- `index.jsp` - Main search page
- `search.jsp` - Search interface
- `searchResults.jsp` - Search results display
- `searchNoResult.jsp` - No results found page
- `searchOptions.jsp` - Search options
- `advance.jsp` - Advanced search
- `help.jsp` - Help page

## Download
### Navigation & Layout
- `header.jsp` - Page header
- `footer.jsp` - Page footer

See [Maven Repository](https://repo1.maven.org/maven2/org/codelibs/fess/fess-webapp-example/).
### Error Handling
- `error/error.jsp` - General error page
- `error/notFound.jsp` - 404 Not Found
- `error/system.jsp` - System error
- `error/redirect.jsp` - Redirect error
- `error/badRequest.jsp` - 400 Bad Request

### User Interface
- `login/index.jsp` - Login page
- `profile/index.jsp` - User profile page

### Cache Display
- `cache.hbs` - Cache display template (Handlebars)

## Requirements

- Java 21 or later
- Maven 3.6 or later
- Fess 15.0 or later

## Installation

See [Plugin](https://fess.codelibs.org/13.9/admin/plugin-guide.html) of Administration guide.
### From Maven Repository

The plugin is available on Maven Central:

```xml
<dependency>
<groupId>org.codelibs.fess</groupId>
<artifactId>fess-webapp-example</artifactId>
<version>15.0.0</version>
</dependency>
```

### Manual Installation

1. Download the plugin JAR from [Maven Repository](https://repo1.maven.org/maven2/org/codelibs/fess/fess-webapp-example/)
2. Follow the [Plugin Installation Guide](https://fess.codelibs.org/admin/plugin-guide.html) in the Fess documentation

### Building from Source

```bash
git clone https://github.com/codelibs/fess-webapp-example.git
cd fess-webapp-example
mvn clean package
```

The compiled JAR will be available in the `target/` directory.

## Development

### Project Structure

```
src/
├── main/
│ ├── java/
│ │ └── org/codelibs/fess/plugin/webapp/helper/
│ │ └── CustomSystemHelper.java
│ └── resources/
│ └── fess+systemHelper.xml
└── test/
├── java/
│ └── org/codelibs/fess/plugin/webapp/helper/
│ └── CustomSystemHelperTest.java
└── resources/
└── test_app.xml
```

### Core Components

#### CustomSystemHelper

The main plugin class that extends Fess's `SystemHelper`:

- **Location**: `src/main/java/org/codelibs/fess/plugin/webapp/helper/CustomSystemHelper.java`
- **Function**: Overrides `parseProjectProperties()` with enhanced error handling
- **System Property**: Sets `fess.webapp.plugin=true` during initialization

#### Configuration

- **DI Configuration**: `src/main/resources/fess+systemHelper.xml`
- **Component Registration**: Maps UI component names to JSP template files
- **Test Configuration**: `src/test/resources/test_app.xml`

### Building and Testing

```bash
# Compile the project
mvn clean compile

# Run tests
mvn test

# Create package
mvn clean package

# Format code
mvn formatter:format

# Check license headers
mvn license:check

# Generate documentation
mvn javadoc:javadoc
```

### Creating Custom Templates

1. Extend the `CustomSystemHelper` class or create your own helper
2. Register your JSP templates in the DI configuration file
3. Ensure your plugin JAR includes the manifest entry: `Fess-WebAppJar=true`

## Configuration

The plugin uses LastaDi dependency injection framework. Template mappings are configured in `fess+systemHelper.xml`:

```xml
<component name="systemHelper" class="org.codelibs.fess.plugin.webapp.helper.CustomSystemHelper">
<postConstruct name="addDesignJspFileName">
<arg>"index"</arg>
<arg>"index.jsp"</arg>
</postConstruct>
<!-- Additional template mappings... -->
</component>
```

## Contributing

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/your-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin feature/your-feature`)
5. Create a Pull Request

### Development Guidelines

- Follow the existing code style and conventions
- Add appropriate test cases for new functionality
- Ensure all tests pass before submitting
- Update documentation as needed

## License

This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.

## Support

- **Documentation**: [Fess Documentation](https://fess.codelibs.org/)
- **Plugin Guide**: [Plugin Installation Guide](https://fess.codelibs.org/admin/plugin-guide.html)
- **Issues**: [GitHub Issues](https://github.com/codelibs/fess-webapp-example/issues)
- **Discussions**: [GitHub Discussions](https://github.com/codelibs/fess-webapp-example/discussions)

## Related Projects

- [Fess](https://github.com/codelibs/fess) - The main Fess search server
- [LastaFlute](https://github.com/lastaflute/lastaflute) - Web framework used by Fess
- [DBFlute](https://github.com/dbflute/dbflute-core) - Database access framework

---

**CodeLibs Project** - https://www.codelibs.org/
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,21 @@
import org.apache.logging.log4j.Logger;
import org.codelibs.fess.helper.SystemHelper;

/**
* Custom system helper for Fess webapp plugin that extends the default SystemHelper.
* This helper enables webapp plugin functionality by setting the appropriate system property
* and provides enhanced error handling for project properties parsing.
*/
public class CustomSystemHelper extends SystemHelper {

/**
* Default constructor for CustomSystemHelper.
* Initializes the custom system helper with webapp plugin capabilities.
Copy link

Copilot AI Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor documentation states it 'Initializes the custom system helper with webapp plugin capabilities' but the constructor only calls super(). The actual webapp plugin initialization happens elsewhere, making this documentation misleading.

Suggested change
* Initializes the custom system helper with webapp plugin capabilities.
* Calls the parent constructor. Webapp plugin capabilities are initialized
* during project properties parsing in the parseProjectProperties method.

Copilot uses AI. Check for mistakes.
*/
public CustomSystemHelper() {
super();
}

private static final Logger logger = LogManager.getLogger(CustomSystemHelper.class);

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
*/
package org.codelibs.fess.plugin.webapp.helper;

import java.nio.file.Path;
import java.nio.file.Paths;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.codelibs.fess.mylasta.direction.FessConfig;
import org.codelibs.fess.util.ComponentUtil;
import org.dbflute.utflute.lastaflute.LastaFluteTestCase;
Expand Down Expand Up @@ -64,4 +72,141 @@ public void tearDown() throws Exception {
public void test_checkProperty() {
assertEquals("true", System.getProperty("fess.webapp.plugin"));
}

public void test_parseProjectProperties_withValidPath() {
// Given
CustomSystemHelper helper = new CustomSystemHelper();
Path validPath = Paths.get("src/test/resources/test_app.xml");

// When
helper.parseProjectProperties(validPath);

// Then
assertEquals("true", System.getProperty("fess.webapp.plugin"));
}

public void test_parseProjectProperties_withNullPath() {
// Given
CustomSystemHelper helper = new CustomSystemHelper();

// When
helper.parseProjectProperties(null);

// Then
assertEquals("true", System.getProperty("fess.webapp.plugin"));
}

public void test_parseProjectProperties_withNonExistentPath() {
// Given
CustomSystemHelper helper = new CustomSystemHelper();
Path nonExistentPath = Paths.get("non/existent/path/project.properties");

// When
helper.parseProjectProperties(nonExistentPath);

// Then
assertEquals("true", System.getProperty("fess.webapp.plugin"));
}

public void test_parseProjectProperties_systemPropertyAlwaysSet() {
// Given
CustomSystemHelper helper = new CustomSystemHelper();
System.clearProperty("fess.webapp.plugin");
assertNull("Property should be cleared initially", System.getProperty("fess.webapp.plugin"));

// When
helper.parseProjectProperties(Paths.get("invalid/path"));

// Then
assertEquals("true", System.getProperty("fess.webapp.plugin"));
}

public void test_parseProjectProperties_multipleCalls() {
// Given
CustomSystemHelper helper = new CustomSystemHelper();
Path testPath = Paths.get("src/test/resources/test_app.xml");

// When
helper.parseProjectProperties(testPath);
helper.parseProjectProperties(testPath);
helper.parseProjectProperties(null);

// Then
assertEquals("true", System.getProperty("fess.webapp.plugin"));
}

public void test_inheritance_extendsSystemHelper() {
// Given
CustomSystemHelper helper = new CustomSystemHelper();

// Then
assertTrue("CustomSystemHelper should extend SystemHelper", helper instanceof org.codelibs.fess.helper.SystemHelper);
Comment on lines +140 to +143
Copy link

Copilot AI Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using instanceof for inheritance testing is fragile and not recommended for unit tests. Consider testing actual behavior instead of class hierarchy, or use reflection-based assertions if inheritance verification is necessary.

Suggested change
CustomSystemHelper helper = new CustomSystemHelper();
// Then
assertTrue("CustomSystemHelper should extend SystemHelper", helper instanceof org.codelibs.fess.helper.SystemHelper);
Class<?> clazz = CustomSystemHelper.class;
// Then
assertEquals("CustomSystemHelper should extend SystemHelper", org.codelibs.fess.helper.SystemHelper.class, clazz.getSuperclass());

Copilot uses AI. Check for mistakes.
}

public void test_loggerConfiguration() {
// Given
LoggerContext context = (LoggerContext) LogManager.getContext(false);
Configuration config = context.getConfiguration();
LoggerConfig loggerConfig = config.getLoggerConfig(CustomSystemHelper.class.getName());

// Then
assertNotNull("Logger should be configured", loggerConfig);
}

public void test_parseProjectProperties_withEmptyPath() {
// Given
CustomSystemHelper helper = new CustomSystemHelper();
Path emptyPath = Paths.get("");

// When
helper.parseProjectProperties(emptyPath);

// Then
assertEquals("true", System.getProperty("fess.webapp.plugin"));
}

public void test_parseProjectProperties_propertyPersistence() {
// Given
CustomSystemHelper helper1 = new CustomSystemHelper();
CustomSystemHelper helper2 = new CustomSystemHelper();
System.clearProperty("fess.webapp.plugin");

// When
helper1.parseProjectProperties(null);
String propertyAfterFirst = System.getProperty("fess.webapp.plugin");
helper2.parseProjectProperties(null);
String propertyAfterSecond = System.getProperty("fess.webapp.plugin");

// Then
assertEquals("true", propertyAfterFirst);
assertEquals("true", propertyAfterSecond);
assertEquals("Property should remain consistent", propertyAfterFirst, propertyAfterSecond);
Comment on lines +172 to +183
Copy link

Copilot AI Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearing system properties in tests can affect other tests running in parallel. Consider using a proper test isolation mechanism or ensuring proper cleanup in tearDown method to avoid test interdependencies.

Suggested change
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);
String originalProperty = System.getProperty("fess.webapp.plugin");
try {
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);
} finally {
if (originalProperty != null) {
System.setProperty("fess.webapp.plugin", originalProperty);
} else {
System.clearProperty("fess.webapp.plugin");
}
}

Copilot uses AI. Check for mistakes.
}

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);
}
}
}