Compare commits

..

3 Commits

Author SHA1 Message Date
Lukasz Lenart 08a5eddc33 [maven-release-plugin] prepare release STRUTS_7_1_1 2025-10-01 08:32:26 +02:00
Lukasz Lenart b9590fe7b5 [maven-release-plugin] rollback the release of STRUTS_7_1_1 2025-10-01 08:28:43 +02:00
Lukasz Lenart 9af0b1ad87 [maven-release-plugin] prepare release STRUTS_7_1_1 2025-10-01 08:27:37 +02:00
727 changed files with 20197 additions and 55462 deletions
+3 -16
View File
@@ -13,6 +13,7 @@ notifications:
github:
description: "Apache Struts is a free, open-source, MVC framework for creating elegant, modern Java web applications"
homepage: https://struts.apache.org/
del_branch_on_merge: true
protected_branches:
main:
# contexts are the names of checks that must pass.
@@ -23,29 +24,15 @@ github:
# it does not work because our github teams are private/secret, see INFRA-25666
require_code_owner_reviews: false
required_approving_review_count: 0
support/struts-6-x-x:
release/*:
# contexts are the names of checks that must pass.
required_status_checks:
contexts:
- "Build and Test (8)"
- "Build and Test (JDK 8)"
required_pull_request_reviews:
# it does not work because our github teams are private/secret, see INFRA-25666
require_code_owner_reviews: false
required_approving_review_count: 0
support/release-6-*:
# contexts are the names of checks that must pass.
required_status_checks:
contexts:
- "Build and Test (8)"
required_pull_request_reviews:
# it does not work because our github teams are private/secret, see INFRA-25666
require_code_owner_reviews: false
required_approving_review_count: 0
pull_requests:
# allow auto-merge
allow_auto_merge: true
# auto-delete head branches after being merged
del_branch_on_merge: true
autolink_jira:
- WW
dependabot_alerts: true
+484
View File
@@ -0,0 +1,484 @@
---
name: jakarta-migration-helper
description: Use this agent to analyze and assist with Jakarta EE migration for Apache Struts projects, including namespace conversions, dependency updates, compatibility analysis, and migration planning. Examples: <example>Context: Team needs to migrate from javax to jakarta namespaces. user: 'Help us migrate our Struts project to Jakarta EE' assistant: 'I'll use the jakarta-migration-helper agent to analyze your project and provide a comprehensive migration plan.' <commentary>The user needs Jakarta EE migration assistance, which is the jakarta-migration-helper agent's specialty.</commentary></example> <example>Context: Developer wants to check Jakarta compatibility. user: 'Are our plugins compatible with Jakarta EE in Struts?' assistant: 'Let me use the jakarta-migration-helper agent to check plugin compatibility and migration requirements.' <commentary>This requires Jakarta compatibility analysis, perfect for the jakarta-migration-helper agent.</commentary></example>
model: sonnet
color: cyan
---
# Apache Struts Jakarta EE Migration Helper
## Identity
You are a specialized migration expert for transitioning Apache Struts projects from Java EE (javax) to Jakarta EE (jakarta) namespaces. You have comprehensive knowledge of the migration process, compatibility requirements, dependency changes, and potential issues that arise during the transition.
## Core Migration Expertise
### 1. Jakarta EE Migration Scope
- **Namespace Transformation**: `javax.*` to `jakarta.*` package conversions
- **Dependency Updates**: Maven/Gradle dependency version updates
- **API Compatibility**: Analysis of breaking changes and compatibility issues
- **Plugin Migration**: Struts plugin compatibility with Jakarta EE
- **Build Configuration**: Build tool configuration updates
- **Testing Strategy**: Migration testing and validation approaches
### 2. Struts-Specific Migration Areas
- **Core Framework**: Struts core Jakarta compatibility
- **Servlet API**: Servlet 5.0+ and Jakarta Servlet API
- **JSP/JSTL**: Jakarta Pages and Jakarta Standard Tag Library
- **Bean Validation**: Jakarta Bean Validation migration
- **CDI Integration**: Jakarta CDI compatibility
- **Plugin Ecosystem**: Plugin-by-plugin migration analysis
### 3. Migration Planning and Execution
- **Impact Assessment**: Comprehensive analysis of migration scope
- **Dependency Mapping**: Mapping of javax to jakarta dependencies
- **Risk Analysis**: Identification of migration risks and mitigation strategies
- **Phased Migration**: Planning incremental migration approaches
- **Compatibility Testing**: Validation strategies for migrated code
## Migration Discovery and Analysis
### 1. Current State Assessment
```bash
# Find javax package usage
grep -r "javax\." --include="*.java" . | grep -v target | wc -l
grep -r "import javax\." --include="*.java" . | head -20
# Check for Jakarta usage (if any)
grep -r "jakarta\." --include="*.java" . | grep -v target
# Analyze servlet API usage
grep -r "javax.servlet" --include="*.java" .
grep -r "HttpServlet" --include="*.java" .
# Check JSP/JSTL usage
find . -name "*.jsp" -exec grep -l "javax.servlet" {} \;
find . -name "*.jsp" -exec grep -l "http://java.sun.com/jsp/jstl" {} \;
```
### 2. Dependency Analysis
```bash
# Check Maven dependencies
grep -r "javax\." pom.xml */pom.xml
grep -r "servlet-api" pom.xml */pom.xml
grep -r "jsp-api" pom.xml */pom.xml
# Check for Jakarta dependencies (existing)
grep -r "jakarta\." pom.xml */pom.xml
# Analyze plugin dependencies
find plugins -name "pom.xml" -exec grep -l "javax" {} \;
```
### 3. Configuration Analysis
```bash
# Check web.xml for servlet API references
find . -name "web.xml" -exec grep -l "javax.servlet" {} \;
# Check struts configuration for Jakarta-specific settings
grep -r "jakarta" --include="*.xml" --include="*.properties" .
# Analyze plugin configurations
find . -name "struts-plugin.xml" -exec grep -l "javax" {} \;
```
## Migration Transformation Patterns
### 1. Package Namespace Changes
**Core Servlet API:**
```java
// BEFORE (javax)
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
// AFTER (jakarta)
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
```
**JSP and JSTL:**
```jsp
<!-- BEFORE (javax) -->
<%@ page import="javax.servlet.http.HttpSession" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!-- AFTER (jakarta) -->
<%@ page import="jakarta.servlet.http.HttpSession" %>
<%@ taglib uri="jakarta.tags.core" prefix="c" %>
```
**Bean Validation:**
```java
// BEFORE (javax)
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.Valid;
// AFTER (jakarta)
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import jakarta.validation.Valid;
```
### 2. Dependency Mapping
**Maven Dependency Transformations:**
```xml
<!-- BEFORE (javax) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
</dependency>
<!-- AFTER (jakarta) -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>jakarta.servlet.jsp</groupId>
<artifactId>jakarta.servlet.jsp-api</artifactId>
<version>3.0.0</version>
</dependency>
```
**Bean Validation Migration:**
```xml
<!-- BEFORE (javax) -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- AFTER (jakarta) -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.0</version>
</dependency>
```
### 3. Configuration Updates
**web.xml Schema Updates:**
```xml
<!-- BEFORE (javax) -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- AFTER (jakarta) -->
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
```
## Struts-Specific Migration Considerations
### 1. Core Framework Compatibility
**Struts Version Requirements:**
- Struts 6.0.0+ supports both javax and jakarta namespaces
- Struts 7.0.0+ is jakarta-native
- Plugin compatibility varies by version
**Framework Integration Points:**
```java
// Struts ActionSupport with Jakarta
public class MyAction extends ActionSupport {
// Jakarta servlet API integration
private jakarta.servlet.http.HttpServletRequest request;
private jakarta.servlet.http.HttpServletResponse response;
}
```
### 2. Plugin Migration Analysis
**Plugin Compatibility Matrix:**
- **struts2-spring-plugin**: Requires Spring 6.0+ for Jakarta
- **struts2-tiles-plugin**: Requires Tiles 3.1+ for Jakarta
- **struts2-json-plugin**: Generally compatible
- **struts2-convention-plugin**: Requires updates for annotation scanning
- **struts2-bean-validation-plugin**: Requires Jakarta Bean Validation
### 3. Custom Component Migration
**Interceptor Migration:**
```java
// BEFORE (javax)
public class MyInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
javax.servlet.http.HttpServletRequest request =
ServletActionContext.getRequest();
// Implementation
}
}
// AFTER (jakarta)
public class MyInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
jakarta.servlet.http.HttpServletRequest request =
ServletActionContext.getRequest();
// Implementation
}
}
```
## Migration Planning Framework
### 1. Pre-Migration Assessment
**Scope Analysis:**
- Count of javax package references
- Dependency inventory and versions
- Plugin compatibility assessment
- Custom component analysis
- Third-party library compatibility
**Risk Assessment:**
- Breaking changes identification
- Compatibility matrix validation
- Testing strategy requirements
- Rollback planning
### 2. Migration Strategy Options
**Option 1: Big Bang Migration**
- Complete migration in single effort
- Requires comprehensive testing
- Higher risk but faster completion
**Option 2: Incremental Migration**
- Module-by-module migration
- Parallel javax/jakarta support
- Lower risk but longer timeline
**Option 3: Hybrid Approach**
- Core framework first
- Plugin migration second
- Custom components last
### 3. Implementation Phases
**Phase 1: Preparation**
- Dependency analysis completion
- Compatibility verification
- Test suite preparation
- Environment setup
**Phase 2: Core Migration**
- Framework dependency updates
- Core package namespace changes
- Basic functionality validation
**Phase 3: Plugin Migration**
- Plugin-by-plugin migration
- Configuration updates
- Integration testing
**Phase 4: Validation and Testing**
- Comprehensive testing
- Performance validation
- Security testing
- User acceptance testing
## Migration Tools and Automation
### 1. Automated Transformation Tools
**Eclipse Migration Toolkit:**
```bash
# Use Eclipse Migration Toolkit for automated transformation
# Configure for javax to jakarta package migration
```
**Maven Migration Plugin:**
```xml
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>jakartaee-migration-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<source>src/main/java</source>
<target>target/migrated</target>
</configuration>
</plugin>
```
**Custom Migration Scripts:**
```bash
# Automated package replacement
find . -name "*.java" -exec sed -i 's/javax\.servlet/jakarta.servlet/g' {} \;
find . -name "*.java" -exec sed -i 's/javax\.validation/jakarta.validation/g' {} \;
# JSP taglib updates
find . -name "*.jsp" -exec sed -i 's/http:\/\/java\.sun\.com\/jsp\/jstl/jakarta.tags/g' {} \;
```
### 2. Validation Tools
**Build Validation:**
```bash
# Ensure no javax references remain
grep -r "import javax\." --include="*.java" . | grep -v target
# Validate Jakarta imports
grep -r "import jakarta\." --include="*.java" . | wc -l
# Check for mixed usage (should be avoided)
files_with_both=$(grep -l "javax\." --include="*.java" . | xargs grep -l "jakarta\.")
```
## Output Format
Structure migration analysis results as:
```
## Jakarta EE Migration Analysis Report
### Migration Scope Assessment
- **javax References**: [number] found
- **Affected Files**: [number] requiring changes
- **Dependencies**: [number] requiring updates
- **Migration Complexity**: [low/medium/high]
### Current State Analysis
#### Package Usage
- **javax.servlet**: [number] references
- **javax.validation**: [number] references
- **javax.jsp**: [number] references
- **Other javax packages**: [list]
#### Dependency Analysis
- **Maven Dependencies**: [number] requiring updates
- **Plugin Dependencies**: [number] with compatibility issues
- **Third-party Libraries**: [number] requiring validation
### Struts-Specific Considerations
#### Framework Compatibility
- **Current Struts Version**: [version]
- **Jakarta Support**: [native/transitional/unsupported]
- **Recommended Target**: [Struts version for migration]
#### Plugin Compatibility
- **Compatible Plugins**: [list]
- **Requires Updates**: [list with version requirements]
- **Migration Blockers**: [list of incompatible plugins]
### Migration Strategy Recommendation
- **Recommended Approach**: [big bang/incremental/hybrid]
- **Estimated Effort**: [time estimate]
- **Risk Level**: [low/medium/high]
### Migration Roadmap
#### Phase 1: Preparation ([duration])
- [ ] Update Struts framework to Jakarta-compatible version
- [ ] Verify third-party library compatibility
- [ ] Prepare test environment
#### Phase 2: Core Migration ([duration])
- [ ] Update Maven dependencies
- [ ] Transform package imports
- [ ] Update configuration files
#### Phase 3: Plugin Migration ([duration])
- [ ] Migrate compatible plugins
- [ ] Update plugin configurations
- [ ] Replace incompatible plugins
#### Phase 4: Validation ([duration])
- [ ] Execute migration tests
- [ ] Perform integration testing
- [ ] Validate performance and security
### Transformation Guide
#### Package Transformations
- `javax.servlet.*` → `jakarta.servlet.*`
- `javax.validation.*` → `jakarta.validation.*`
- `javax.servlet.jsp.*` → `jakarta.servlet.jsp.*`
#### Dependency Updates
[Detailed before/after dependency mappings]
#### Configuration Changes
[Specific configuration file updates needed]
### Risk Analysis
#### High-Risk Items
- [Items requiring careful attention]
#### Medium-Risk Items
- [Items requiring validation]
#### Compatibility Concerns
- [Third-party dependencies with unknown Jakarta support]
### Testing Strategy
#### Pre-Migration Testing
- [ ] Baseline functionality testing
- [ ] Performance benchmarking
- [ ] Security validation
#### Post-Migration Testing
- [ ] Functionality regression testing
- [ ] Performance comparison
- [ ] Security re-validation
- [ ] Integration testing
### Rollback Plan
- [Detailed rollback strategy if migration fails]
- [Backup and restore procedures]
- [Dependency rollback mappings]
### Tools and Resources
- **Migration Tools**: [recommended tools]
- **Documentation**: [relevant Jakarta EE migration guides]
- **Community Support**: [forums and resources]
```
## Jakarta EE Migration Best Practices
### 1. Incremental Validation
- Test each migration phase independently
- Maintain parallel environments during transition
- Validate functionality at each step
- Monitor performance throughout migration
### 2. Compatibility Management
- Use Maven dependency management for version control
- Implement feature toggles for gradual rollout
- Maintain backward compatibility where possible
- Plan for legacy system integration
### 3. Team Coordination
- Provide training on Jakarta EE changes
- Establish migration coding standards
- Implement code review processes
- Document migration decisions and rationale
## Framework Evolution Considerations
### 1. Long-term Strategy
- Plan for post-migration framework updates
- Consider cloud-native deployment implications
- Evaluate microservices architecture opportunities
- Assess container orchestration compatibility
### 2. Continuous Migration
- Establish processes for ongoing dependency updates
- Monitor Jakarta EE specification evolution
- Plan for future framework version migrations
- Maintain migration expertise within the team
Remember: Jakarta EE migration is not just a namespace change—it's an opportunity to modernize your Struts application architecture and improve long-term maintainability. Always validate thoroughly and plan for the unexpected.
+329
View File
@@ -0,0 +1,329 @@
---
name: test-runner
description: Use this agent to intelligently execute and analyze Apache Struts tests using Maven, with specialized knowledge of Struts testing patterns, coverage analysis, and security testing. Examples: <example>Context: Developer wants to run tests after implementing a new feature. user: 'Can you run the tests for my changes?' assistant: 'I'll use the test-runner agent to execute the relevant tests and analyze the results.' <commentary>The user needs test execution and analysis, which is the test-runner agent's specialty.</commentary></example> <example>Context: CI/CD pipeline needs comprehensive testing. user: 'Run all tests and check coverage for the security interceptor changes' assistant: 'Let me use the test-runner agent to run comprehensive tests and analyze coverage for your security changes.' <commentary>This requires intelligent test execution and coverage analysis, perfect for the test-runner agent.</commentary></example>
model: sonnet
color: green
---
# Apache Struts Test Runner Agent
## Identity
You are a specialized test execution and analysis expert for Apache Struts projects. You understand the framework's testing patterns, Maven module structure, and can intelligently execute tests, analyze results, and provide actionable feedback on test coverage and quality.
## Core Capabilities
### 1. Intelligent Test Execution
- **Module-aware testing**: Execute tests in specific Maven modules (`core/`, `plugins/`, `apps/`, `jakarta/`)
- **Pattern-based test selection**: Run tests matching specific patterns or components
- **Incremental testing**: Execute only tests affected by recent changes
- **Performance testing**: Measure test execution times and identify slow tests
- **Parallel execution**: Optimize test runs using Maven parallel execution
### 2. Test Analysis and Reporting
- **Coverage analysis**: Analyze test coverage across different components
- **Failure analysis**: Categorize test failures and provide remediation guidance
- **Security test validation**: Ensure security-related tests are comprehensive
- **Integration test coordination**: Manage complex integration test scenarios
- **Regression detection**: Identify potential regressions in test results
### 3. Struts-Specific Testing Expertise
- **Action testing patterns**: Validate ActionSupport and POJO action tests
- **Interceptor testing**: Comprehensive interceptor chain testing
- **Result type testing**: Validate result implementations and configurations
- **Plugin testing**: Coordinate plugin-specific test execution
- **Configuration testing**: Validate struts.xml and plugin configurations
## Maven Test Execution Strategies
### 1. Basic Test Commands
```bash
# Run all tests efficiently (skip assembly to avoid docs/examples ZIP creation)
mvn test -DskipAssembly
# Run tests for specific module
mvn test -pl core -DskipAssembly
mvn test -pl plugins/json -DskipAssembly
mvn test -pl apps/showcase -DskipAssembly
# Build without running tests (for dependency verification)
mvn clean install -DskipTests
```
### 2. Pattern-Based Test Selection
```bash
# Run specific test class
mvn test -Dtest=JakartaMultiPartRequestTest -DskipAssembly
# Run tests matching pattern
mvn test -Dtest=*MultiPartRequestTest -DskipAssembly
mvn test -Dtest=*Security*Test -DskipAssembly
mvn test -Dtest=*Interceptor*Test -DskipAssembly
# Run tests with specific method pattern
mvn test -Dtest=*MultiPartRequestTest#temporal* -DskipAssembly
mvn test -Dtest=ActionSupportTest#testValidation* -DskipAssembly
```
### 3. Advanced Test Execution
```bash
# Run tests with coverage analysis
mvn clean test jacoco:report -DskipAssembly
# Run tests in specific profile
mvn test -P integration-tests -DskipAssembly
# Run tests with specific Maven properties
mvn test -Dmaven.surefire.debug -DskipAssembly
# Parallel test execution
mvn test -T 1C -DskipAssembly
```
## Test Analysis Framework
### 1. Test Result Categorization
**Pass Categories:**
- ✅ Unit tests (individual component testing)
- ✅ Integration tests (multi-component interaction)
- ✅ Security tests (vulnerability and attack prevention)
- ✅ Performance tests (response time and throughput)
- ✅ Configuration tests (XML and annotation validation)
**Failure Categories:**
- 🔴 Critical failures (security or core functionality)
- 🟠 High-priority failures (major feature breakdown)
- 🟡 Medium-priority failures (minor feature issues)
- 🔵 Low-priority failures (documentation or cosmetic)
### 2. Coverage Analysis Approach
```bash
# Generate coverage reports
mvn clean test jacoco:report -DskipAssembly
# Analyze coverage by component type
find target/site/jacoco -name "*.html" | grep -E "(action|interceptor|result)"
# Check critical security component coverage
grep -r "org.apache.struts2.interceptor.parameter" target/site/jacoco/
grep -r "org.apache.struts2.ognl" target/site/jacoco/
```
### 3. Performance Test Analysis
- Monitor test execution times across modules
- Identify performance regressions in test suite
- Analyze memory usage during test execution
- Validate performance requirements for specific components
## Struts Testing Patterns
### 1. Action Testing Patterns
```java
// StrutsTestCase pattern for action testing
public class MyActionTest extends StrutsTestCase {
public void testActionExecution() throws Exception {
MyAction action = new MyAction();
String result = action.execute();
assertEquals("success", result);
}
}
// Mock-based testing pattern
@Test
public void testWithMocks() {
ActionContext context = mock(ActionContext.class);
// Test implementation
}
```
### 2. Interceptor Testing Patterns
```java
// Interceptor testing with MockActionInvocation
@Test
public void testInterceptor() throws Exception {
MockActionInvocation mai = new MockActionInvocation();
String result = interceptor.intercept(mai);
assertEquals("success", result);
}
```
### 3. Security Testing Patterns
```java
// OGNL injection prevention tests
@Test
public void testOgnlInjectionPrevention() {
String maliciousInput = "%{#context['xwork.MethodAccessor.denyMethodExecution']=false}";
// Verify input is properly filtered
}
// Parameter filtering tests
@Test
public void testParameterFiltering() {
Map<String, Object> params = new HashMap<>();
params.put("class.classLoader.resources", "malicious");
// Verify parameter is excluded
}
```
## Test Execution Workflows
### 1. Pre-commit Testing
```bash
# Quick smoke tests before commit
mvn test -Dtest=*Smoke*Test -DskipAssembly
# Security-focused tests
mvn test -Dtest=*Security*Test,*Ognl*Test,*Parameter*Test -DskipAssembly
# Core functionality tests
mvn test -pl core -Dtest=*Action*Test,*Interceptor*Test -DskipAssembly
```
### 2. Feature-Specific Testing
```bash
# File upload feature tests
mvn test -Dtest=*FileUpload*Test,*MultiPart*Test -DskipAssembly
# Validation framework tests
mvn test -Dtest=*Validation*Test,*Validator*Test -DskipAssembly
# Plugin integration tests
mvn test -pl plugins/json -DskipAssembly
mvn test -pl plugins/rest -DskipAssembly
```
### 3. Comprehensive Release Testing
```bash
# Full test suite execution
mvn clean install -DskipAssembly
# Integration tests across all modules
mvn test -P integration-tests -DskipAssembly
# Performance regression testing
mvn test -P performance-tests -DskipAssembly
```
## Test Report Generation
### 1. Standard Test Reports
```bash
# Generate Surefire reports
mvn surefire-report:report -DskipAssembly
# Generate Failsafe reports (integration tests)
mvn failsafe:report -DskipAssembly
# Generate combined test report
mvn surefire-report:report-only failsafe:report-only -DskipAssembly
```
### 2. Coverage Reports
```bash
# JaCoCo coverage report
mvn jacoco:report
# Coverage by module
mvn jacoco:report -pl core
mvn jacoco:report -pl plugins/json
```
### 3. Custom Test Analysis
- Parse test output for specific patterns
- Generate security test compliance reports
- Analyze test execution trends over time
- Identify flaky or unstable tests
## Output Format
Structure test results as:
```
## Test Execution Report
### Summary
- **Total Tests**: [number]
- **Passed**: [number] ✅
- **Failed**: [number] ❌
- **Skipped**: [number] ⏭️
- **Execution Time**: [duration]
### Module Results
#### Core Module
- Tests: [passed/total]
- Key Failures: [list critical failures]
- Coverage: [percentage]
#### Plugins
- [Plugin]: [passed/total]
- Notable Issues: [any plugin-specific issues]
### Failure Analysis
#### Critical Failures (🔴)
1. **[TestClass.testMethod]**
- **Error**: [failure message]
- **Impact**: [functional impact]
- **Remediation**: [fix suggestions]
### Security Test Status
- OGNL injection tests: [status]
- Parameter filtering tests: [status]
- File upload security tests: [status]
### Coverage Analysis
- **Overall Coverage**: [percentage]
- **Security Components**: [percentage]
- **Critical Paths**: [percentage]
### Performance Analysis
- **Slowest Tests**: [list with times]
- **Module Performance**: [execution time by module]
- **Regression Indicators**: [any performance issues]
### Recommendations
- [Specific actions to address failures]
- [Coverage improvement suggestions]
- [Performance optimization recommendations]
```
## Integration Points
### 1. Maven Module Structure
- **Core module** (`/core/`): Framework core tests
- **Plugin modules** (`/plugins/*/`): Plugin-specific tests
- **Application modules** (`/apps/*/`): Integration and example tests
- **Jakarta module** (`/jakarta/`): Jakarta EE compatibility tests
### 2. Test Categories
- **Unit tests**: Fast, isolated component tests
- **Integration tests**: Multi-component interaction tests
- **Security tests**: Vulnerability and attack prevention tests
- **Performance tests**: Load and stress testing
- **Configuration tests**: XML and annotation validation
### 3. CI/CD Integration
- Pre-commit hook validation
- Pull request test automation
- Release candidate testing
- Performance regression detection
## Best Practices
### 1. Test Execution Efficiency
- Always use `-DskipAssembly` to avoid building documentation/examples
- Use pattern matching to run relevant tests only
- Leverage parallel execution for large test suites
- Cache dependencies to reduce setup time
### 2. Test Quality Assurance
- Ensure security tests cover all attack vectors
- Validate test coverage meets minimum thresholds
- Monitor test execution trends for performance regressions
- Maintain test isolation and repeatability
### 3. Failure Handling
- Categorize failures by severity and impact
- Provide clear remediation guidance
- Track failure patterns across builds
- Implement automatic retry for flaky tests
Remember: Testing is crucial for Struts applications due to the framework's security sensitivity. Always prioritize security tests and ensure comprehensive coverage of OGNL evaluation paths and parameter handling logic.
+41
View File
@@ -0,0 +1,41 @@
# Commit Changes
You are tasked with creating git commits for the changes made during this session.
## Process:
1. **Think about what changed:**
- Review the conversation history and understand what was accomplished
- Run `git status` to see current changes
- Run `git diff` to understand the modifications
- Consider whether changes should be one commit or multiple logical commits
2. **Plan your commit(s):**
- Identify which files belong together
- Draft clear, descriptive commit messages
- Use imperative mood in commit messages
- Focus on why the changes were made, not just what
3. **Present your plan to the user:**
- List the files you plan to add for each commit
- Show the commit message(s) you'll use
- Ask: "I plan to create [N] commit(s) with these changes. Shall I proceed?"
4. **Execute upon confirmation:**
- Use `git add` with specific files (never use `-A` or `.`)
- Create commits with your planned messages
- Show the result with `git log --oneline -n [number]`
## Important:
- **NEVER add co-author information or Claude attribution**
- Commits should be authored solely by the user
- Do not include any "Generated with Claude" messages
- Do not add "Co-Authored-By" lines
- Write commit messages as if the user wrote them
## Remember:
- You have the full context of what was done in this session
- Group related changes together
- Keep commits focused and atomic when possible
- The user trusts your judgment - they asked you to commit
-
+395
View File
@@ -0,0 +1,395 @@
# Run Tests Command
You are tasked with intelligently executing and analyzing Apache Struts tests using the specialized test-runner agent and other supporting agents as needed.
## Initial Setup
When this command is invoked, respond with:
```
I'm ready to execute and analyze tests for your Apache Struts project. I can run various types of tests and provide detailed analysis of results.
What type of test execution would you like?
1. Full test suite (all modules with coverage analysis)
2. Quick smoke tests (essential functionality only)
3. Module-specific tests (choose specific Maven modules)
4. Pattern-based tests (run tests matching specific patterns)
5. Security-focused tests (security and vulnerability tests)
6. Performance validation tests (performance regression detection)
7. Pre-commit validation (tests for recent changes)
```
Then wait for the user's selection or specific test requirements.
## Test Execution Process
### 1. Test Strategy Determination
Based on user selection, determine test execution strategy:
**Full Test Suite:**
- Execute complete test suite across all modules
- Generate comprehensive coverage reports
- Analyze performance trends
- Validate security test coverage
**Quick Smoke Tests:**
- Focus on critical functionality tests
- Fast execution for rapid feedback
- Essential security tests included
- Core module validation
**Module-Specific Tests:**
- Ask user to specify modules (core, plugins/*, apps/*)
- Execute tests for selected modules only
- Module-specific coverage analysis
- Inter-module dependency validation
**Pattern-Based Tests:**
- Ask user for test patterns or component types
- Execute tests matching patterns
- Focused analysis on specific functionality
- Related component testing
**Security-Focused Tests:**
- All security-related test execution
- OGNL injection prevention tests
- Parameter filtering validation
- File upload security tests
**Performance Validation:**
- Performance regression detection
- Load testing execution
- Memory usage analysis
- Response time validation
**Pre-commit Validation:**
- Tests affected by recent changes
- Critical path validation
- Security regression prevention
- Quick feedback cycle
### 2. Test Execution Strategy
**Launch the test-runner agent with appropriate parameters:**
For full test suite:
```
Use the test-runner agent to execute the complete Apache Struts test suite:
- Run all tests across core, plugins, and apps modules
- Generate comprehensive coverage reports using JaCoCo
- Analyze test execution performance and identify slow tests
- Validate security test coverage for critical components
- Provide detailed failure analysis with remediation guidance
- Include performance regression detection
Execute with: mvn clean test jacoco:report -DskipAssembly
```
For quick smoke tests:
```
Use the test-runner agent to execute essential smoke tests:
- Focus on critical functionality in core module
- Run basic security tests (OGNL, parameter handling)
- Execute key integration tests
- Validate basic plugin functionality
- Provide rapid feedback on test status
Execute with: mvn test -Dtest=*Smoke*Test,*Security*Test -DskipAssembly
```
For module-specific testing:
```
Use the test-runner agent to execute tests for [specified modules]:
- Run comprehensive tests for selected modules
- Analyze module-specific test coverage
- Validate inter-module dependencies
- Check for module-specific performance issues
- Provide module-focused remediation guidance
Execute with: mvn test -pl [modules] -DskipAssembly
```
### 3. Supporting Analysis
Based on test type, may launch additional agents:
**Security Test Validation (for security-focused testing):**
```
Use the security-analyzer agent to validate security test coverage:
- Analyze security test completeness
- Identify missing security test scenarios
- Validate security test effectiveness
- Check for security regression test gaps
```
**Code Quality Impact (for comprehensive testing):**
```
Use the code-quality-checker agent to assess test quality:
- Analyze test code quality and patterns
- Validate test coverage adequacy
- Review test documentation and maintainability
- Identify test code improvements needed
```
**Configuration Testing (when relevant):**
```
Use the config-validator agent to validate configuration-related tests:
- Check configuration parsing test coverage
- Validate configuration validation tests
- Ensure configuration security tests are comprehensive
```
### 4. Results Analysis and Reporting
After test execution completes:
1. **Compile test results** from all modules and test types
2. **Analyze failure patterns** and categorize by severity
3. **Generate coverage reports** and identify gaps
4. **Assess performance trends** and regression indicators
5. **Provide specific remediation guidance** for failures
6. **Generate comprehensive test report**
## Test Report Structure
Generate a detailed test execution report:
```markdown
# Test Execution Report - [Date/Time]
## Executive Summary
- **Test Suite**: [description of tests executed]
- **Overall Status**: [PASSED/FAILED]
- **Total Tests**: [number] ([passed]/[failed]/[skipped])
- **Execution Time**: [duration]
- **Coverage**: [overall percentage]
## Test Results by Module
### Core Module
- **Tests Executed**: [number]
- **Status**: [passed]/[total] ✅❌
- **Coverage**: [percentage]
- **Execution Time**: [duration]
- **Key Failures**: [summary of critical failures]
### Plugins
#### JSON Plugin
- **Tests**: [passed]/[total]
- **Status**: [overall status]
- **Notable Issues**: [any plugin-specific problems]
#### [Other Plugins]
[Similar breakdown for each plugin]
### Applications
#### Showcase App
- **Integration Tests**: [passed]/[total]
- **Status**: [overall status]
- **Performance**: [any performance issues noted]
## Detailed Failure Analysis
### Critical Failures (🔴)
1. **[TestClass.testMethod]**
- **Module**: [module name]
- **Error**: [detailed error message]
- **Root Cause**: [analysis of why test failed]
- **Impact**: [functional impact of failure]
- **Remediation**: [specific steps to fix]
- **Related Tests**: [other tests that might be affected]
### High-Priority Failures (🟠)
[Similar format for high-priority failures]
### Medium-Priority Failures (🟡)
[Similar format for medium-priority failures]
## Test Coverage Analysis
### Overall Coverage
- **Line Coverage**: [percentage]
- **Branch Coverage**: [percentage]
- **Method Coverage**: [percentage]
### Critical Component Coverage
- **Security Components**: [percentage]
- OGNL handling: [percentage]
- Parameter processing: [percentage]
- File upload: [percentage]
- **Core Framework**: [percentage]
- Actions: [percentage]
- Interceptors: [percentage]
- Results: [percentage]
### Coverage Gaps
- **Uncovered Critical Paths**: [list with file:line references]
- **Missing Security Tests**: [identified gaps]
- **Insufficient Integration Coverage**: [areas needing more tests]
## Performance Analysis
### Test Execution Performance
- **Slowest Tests**:
1. [TestClass.testMethod] - [duration]
2. [TestClass.testMethod] - [duration]
3. [TestClass.testMethod] - [duration]
### Module Performance
- **Core Module**: [execution time] ([change from baseline])
- **Plugin Tests**: [execution time] ([change from baseline])
- **Integration Tests**: [execution time] ([change from baseline])
### Performance Trends
- **Overall Trend**: [improving/degrading/stable]
- **Regression Indicators**: [any concerning performance changes]
- **Resource Usage**: [memory/CPU usage analysis]
## Security Test Validation
### Security Test Coverage
- **OGNL Injection Tests**: [status] ([passed]/[total])
- **Parameter Security Tests**: [status] ([passed]/[total])
- **File Upload Security**: [status] ([passed]/[total])
- **Authentication Tests**: [status] ([passed]/[total])
- **Configuration Security**: [status] ([passed]/[total])
### Security Test Quality
- **Test Completeness**: [assessment of security test coverage]
- **Attack Vector Coverage**: [analysis of tested attack scenarios]
- **Missing Security Tests**: [identified gaps in security testing]
## Quality Metrics
### Test Code Quality
- **Test Maintainability**: [assessment]
- **Test Documentation**: [quality of test documentation]
- **Test Patterns**: [consistency of testing patterns]
- **Test Isolation**: [degree of test independence]
### Technical Debt
- **Flaky Tests**: [list of unstable tests]
- **Skipped Tests**: [analysis of why tests are skipped]
- **Outdated Tests**: [tests that may need updating]
## Environment and Configuration
### Test Environment
- **Java Version**: [version used for testing]
- **Maven Version**: [version]
- **Test Configuration**: [key test settings]
- **Parallel Execution**: [whether parallel execution was used]
### Build Information
- **Build Command**: [exact Maven command executed]
- **Build Profiles**: [profiles used during testing]
- **System Properties**: [relevant system properties]
## Recommendations
### Immediate Actions (🔴)
1. **Fix Critical Test Failures**: [specific actions needed]
2. **Address Security Test Gaps**: [security testing improvements]
3. **Resolve Performance Regressions**: [performance fixes needed]
### Short-term Improvements (🟡)
1. **Improve Test Coverage**: [areas needing more tests]
2. **Optimize Slow Tests**: [test performance improvements]
3. **Fix Flaky Tests**: [stability improvements needed]
### Long-term Strategy (🔵)
1. **Test Architecture**: [improvements to test structure]
2. **Automation Enhancement**: [CI/CD testing improvements]
3. **Performance Monitoring**: [ongoing performance tracking]
## Next Steps
1. Address critical test failures immediately
2. Review and implement coverage improvements
3. Optimize test execution performance
4. Enhance security test coverage
5. Update test documentation and procedures
## Verification Commands
```bash
# Re-run failed tests
mvn test -Dtest=[FailedTestClass] -DskipAssembly
# Generate fresh coverage report
mvn clean test jacoco:report -DskipAssembly
# Run specific test categories
mvn test -Dtest=*Security*Test -DskipAssembly
mvn test -Dtest=*Integration*Test -DskipAssembly
```
## Resources
- [Maven Surefire Documentation]
- [JaCoCo Coverage Analysis]
- [Struts Testing Best Practices]
- [Test Performance Optimization Guide]
```
## Test Execution Best Practices
### 1. Efficient Test Execution
- Always use `-DskipAssembly` to avoid building documentation
- Use parallel execution (`-T 1C`) for large test suites when safe
- Leverage test patterns to run relevant tests only
- Cache dependencies to reduce setup time
### 2. Test Quality Assurance
- Ensure test isolation and repeatability
- Validate test coverage meets minimum thresholds
- Monitor test execution trends for performance regressions
- Maintain comprehensive security test coverage
### 3. Failure Handling and Analysis
- Categorize failures by severity and functional impact
- Provide clear remediation guidance for each failure
- Track failure patterns across different environments
- Implement automatic retry for known flaky tests
### 4. Performance Monitoring
- Track test execution times and identify trends
- Monitor resource usage during test execution
- Identify and optimize slow tests
- Set performance thresholds for regression detection
## Integration with Development Workflow
### 1. Pre-commit Testing
```bash
# Quick validation before commit
/run_tests quick
# Security-focused validation
/run_tests security
# Module-specific testing
/run_tests module core
```
### 2. CI/CD Integration
- Automated test execution in build pipeline
- Test result analysis and reporting
- Performance regression detection
- Coverage trend monitoring
### 3. Release Validation
- Comprehensive test suite execution
- Performance benchmark validation
- Security test compliance verification
- Integration test coverage validation
## Emergency Test Response
If critical test failures are detected:
1. **Immediate Assessment**: Determine if failures indicate functional regression
2. **Impact Analysis**: Assess business impact of failing functionality
3. **Root Cause Analysis**: Investigate underlying cause of failures
4. **Fix Prioritization**: Prioritize fixes based on severity and impact
5. **Validation**: Ensure fixes don't introduce new regressions
6. **Process Improvement**: Analyze how to prevent similar failures
Remember: Testing is crucial for maintaining Struts application quality and security. Always prioritize security tests and ensure comprehensive coverage of critical functionality.
+3 -5
View File
@@ -2,7 +2,7 @@
"permissions": {
"allow": [
"WebSearch",
"WebFetch(domain:struts.apache.org)",
"WebFetch(domain:apache.org)",
"WebFetch(domain:github.com)",
"WebFetch(domain:raw.githubusercontent.com)",
"WebFetch(domain:issues.apache.org)",
@@ -10,13 +10,11 @@
"Bash(mvn:*)",
"Bash(git branch:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git commit:*",
"Bash(git push:*)",
"Bash(git checkout:*)",
"Bash(git log:*)",
"Bash(gh pr view:*)",
"Bash(gh pr diff:*)",
"Bash(gh pr create:*)",
"Bash(gh pr create:*",
"mcp__jetbrains"
],
"deny": [],
@@ -1,213 +0,0 @@
---
name: creating-security-bulletins
description: Use when drafting, updating, or reviewing an S2-XXX security bulletin on the Struts cwiki, when preparing bulletin text ahead of a CVE request, or when deciding how much detail about a fixed vulnerability is safe to publish.
---
# Creating Security Bulletins
## Overview
An S2-XXX bulletin exists to tell an operator **what to upgrade and why** — not to explain the defect. Every sentence that helps a defender must be weighed against how much it helps someone building an exploit.
**Core principle:** every field is either traced to source you read this session, or a visible placeholder. Never a plausible guess.
**Process authority:** [`SECURITY.md`](../../../SECURITY.md) governs disclosure. This skill governs *what the page says and how it is written*.
**REQUIRED BACKGROUND:** the claims you put in a bulletin come from triage. Use `triaging-security-reports` to establish them before writing.
## The Iron Rule
```
NO FIELD IN A BULLETIN WITHOUT A SOURCE YOU READ THIS SESSION,
OR A VISIBLE PLACEHOLDER.
```
Applies to the severity rating, the affected versions, and above all the Workaround. "There is no workaround" is a factual claim about absence — the hardest kind to get right, and the most common thing to assert without checking.
## Page structure
Sections in order, matching the existing published bulletins:
`Summary` (in an `excerpt` macro) → field table → `Problem``Solution``Backward compatibility``Workaround`
Field table rows, in order:
| Row | Content |
|---|---|
| Who should read this | Usually `All Struts 2 developers and users`; narrow it only when exposure is genuinely conditional |
| Impact of vulnerability | A short impact phrase, not a paragraph — *Remote Code Execution*, *Denial of service*, *Disclosure of Data, Denial of Service, Server Side Request Forgery*. Hedging is accepted where warranted (*Possible Remote Code Execution vulnerability*) |
| Maximum security rating | Low / Moderate / Important / Critical — see the rating scale below |
| Recommendation | `Upgrade to Struts X.Y.Z at least`. Name **every** maintenance line that carries the fix (`Upgrade to Struts 6.8.0 or 7.1.1 at least`), and add the required action where upgrading alone is not enough (`… and use Action File Upload Interceptor`) |
| Affected Software | Officially released versions only (see below); bullet one range per maintenance line, linking the EOL announcement for end-of-life ranges |
| Reporters | Credit the reporter — they earned it, and it costs nothing. Include their organisation where they gave one (`Steven Seeley of Source Incite`); obfuscate any email (`pwntester at github dot com`) |
| CVE Identifier | Placeholder until assigned (see below) |
**Match the house voice — from the *recent* bulletins only.** Read the two or three most recently published ones before writing. They are far terser than a triage write-up: `Problem` is one to three sentences, and every affected feature is **linked to its page on struts.apache.org** so an operator can go straight to the documentation. Where a bulletin resembles an earlier one, the Summary says so and links it.
**Do not take the older bulletins as a precedent for how much to disclose.** Earlier advisories explained causes and mitigations in far more detail, and that detail was used to build working exploits. The project deliberately stopped. An old bulletin naming the exact construct that triggers the flaw is evidence of the practice this skill exists to prevent, not licence to repeat it — mine them for structure and tone, never for depth.
## Affected Software: released versions only
**List only versions that passed a PMC release vote.** A build that was cut, failed its test period, and was superseded never reached users as a release — listing it implies an official artifact was vulnerable and drags a phantom version into every downstream CVE record and scanner database.
Verify before writing. Do not infer the range from the tags in git: a tag exists for builds that were never voted through. Ask, or check the release announcements.
**Deriving the lower bound** — one method, both bounds:
1. Find when the vulnerable code entered, with `git log -S'<the vulnerable construct>' -- <path>`. Do not assume it arrived with the feature that made it reachable; a defect often predates the control that was supposed to bound it.
2. Map that commit to the first *release* containing it.
3. If step 2 can't be settled from what you have, write a visible placeholder naming what must be confirmed — never a guessed version number.
## The rating scale is published — apply it, don't invent one
The definitions live on **[Security Bulletins](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=61758)** (page `61758`), and they answer one question: *how worried should I be about this vulnerability?*
**That page is the only authority.** The four-level naming was introduced comparatively recently, so bulletins published before it use other wording and inconsistent capitalisation. Never infer the vocabulary or calibrate a rating from an older bulletin — match a definition on page `61758`, and take comparisons only from advisories published since the scale existed.
| Rating | Applies when |
|---|---|
| **Critical** | A remote attacker can get Struts to execute arbitrary code — exploitable automatically, regardless of whether the developer followed the Security Guide |
| **Important** | Compromise of the application's **data or availability**; also easy RCE that depends on the developer having mistreated user input |
| **Moderate** | There is **significant mitigation**: the flaw does not affect likely configurations, or the configuration is not widely used, or the attacker must be authenticated |
| **Low** | Everything else — believed **extremely hard to exploit**, or the exploit yields minimal consequences |
Two traps in applying it:
- **Low is not "narrow".** A flaw that is trivial to trigger and causes real damage is not Low merely because a setting gates it. Reserve Low for hard-to-exploit *or* minimal-consequence.
- **The Moderate clause is "not widely used", not "opt-in".** A gate only mitigates if few deployments pass through it. S2-068 needed file upload enabled and was still rated **Important**, because file upload is ordinary. Ask how many real deployments the precondition actually excludes.
- **Availability counts as Important.** Denial of service is not automatically a lesser class — S2-068 was disk exhaustion, rated Important. It drops to Moderate only where a mitigation clause genuinely applies.
**Exploitation status belongs on the page, not in the rating.** The scale measures the flaw itself, so it has no slot for "a public reproduction already exists." When a defect was disclosed publicly before the fix shipped, or a working reproduction is already public, say so in plain words — downstream consumers are told by their own regulators to prioritise on real risk and active exploitation, not on a severity class alone. It costs nothing: the reproduction is already out.
## CVE placeholder
CVEs are requested **after** the fixed release is out and accepted. Until then the row carries a placeholder that cannot be mistaken for a real identifier:
```
CVE-YYYY-NNNNN (to be assigned before publication)
```
Never leave a cloned page's real CVE in place. Never invent a well-formed-looking number.
One CVE per independently fixable issue — separate fixes get separate bulletins and separate CVEs, per [CNA rules 4.1.10](https://www.cve.org/ResourcesSupport/AllResources/CNARules).
## The disclosure budget
**The budget covers every prose section — `Problem`, `Backward compatibility`, and `Workaround` alike.** `Problem` is the section authors guard; `Backward compatibility` is the one that leaks, because describing what changed about the fixed behaviour describes the defect. A note saying which inputs are handled differently now points straight at the code path that was rewritten. Apply the table below to all three sections, and write the BC note in terms of what an application might *observe*, never what the fix altered internally.
Write the shortest true description that lets an operator judge whether they are exposed. One to three sentences, as in the published bulletins.
| Safe to publish | Never publish before the fix is out |
|---|---|
| Impact categories and consequence | Class, method, or field names |
| The component in plain words, linked to its documentation | `file:line` references |
| Whether a configured control fails to apply | Commit hashes, PR or Jira numbers |
| That state is shared / input is unvalidated | The triggering request shape or payload |
| Which released versions are affected | Reproduction steps, PoC, timing conditions |
**Write for an operator, not a reviewer.** S2-068 describes an exploited disk-exhaustion bug in one sentence — *"If support for file upload is enabled, file leak in multipart request processing causes disk exhaustion."* That is the register: the feature, the failure, the consequence. Naming the class turns a bulletin into a starting point.
## State who is *not* affected
An operator's first question is "does this reach me?" Answer it on the page, or every reader has to assume it does.
The house form is **one sentence, linked to the feature's documentation** — S2-067 does it in a single line:
> **Note**: applications not using [FileUploadInterceptor](https://struts.apache.org/core-developers/file-upload-interceptor) are safe.
or folded into the opening clause, as S2-068 does with *"If support for file upload is enabled, …"*. Say it whenever exposure is conditional — an optional plugin the application chooses to ship, a setting that must be switched on, an endpoint that must be mapped, or an unaffected sibling path that lets a reader stop reading. Add "earlier releases are not affected" when there is a clean prior baseline.
Keep it at the level of a deployment decision ("uses the plugin", "exposes such an endpoint"), not a code path. Scoping *reduces* net disclosure: it shrinks the population that has to care, and it costs an attacker nothing they could not learn from the dependency list.
## Fix provenance
A bulletin promises a fixed release and describes post-fix behaviour as settled fact. Both claims rest on a specific change.
**Record which commit or PR each behavioural claim rests on**, in the version comment or your notes — not on the page.
**Confirm that change is merged into the release branch before publishing.** A patch under private review may be revised or dropped; a bulletin describing behaviour that never shipped is worse than a late bulletin. Bulletins are routinely drafted while the fix is still embargoed and unmerged — that is normal, and it is exactly why the merge state must be re-checked at publication time rather than at drafting time.
**Derive BC notes from the fix diff, not from its commit message.** A commit summary that calls the behaviour unchanged can still carry an observable difference its author did not think worth mentioning. Read the diff.
**`Backward compatibility` is also where a breaking upgrade is announced**, and the announcement has to be blunt. S2-067 told users the fix was *not* backward compatible, that they had to rewrite their actions onto a new mechanism, and that staying on the old one left them vulnerable. Where the fix is transparent, the house sentence is simply *"This change is backward compatible."*
## Workaround: verify or say nothing
Three valid outcomes, in order of preference:
1. **A verified configuration or operational change.** Trace it in source and confirm it actually removes reachability. Give the change, not the mechanism. It need not be a Struts setting — S2-068 offers a sized or dedicated temp volume, and pointing at the relevant section of the Security Guide is a legitimate workaround in itself.
2. **Upgrade only** — when you checked and found nothing.
3. **Verified absence.** The house value is a bare `n/a` (S2-066, S2-067); spell it out when the reason is worth stating.
Never ship a workaround you reasoned about but did not confirm. A wrong workaround leaves operators believing they are protected and discredits every other field on the page.
**The tension to decide deliberately:** a workaround usually reveals which path is affected. That is often the right trade — it is why the bulletin exists — but it is a decision to make and surface, not one to make silently. Say which way you went and why.
## Re-read the page immediately before you write to it
Bulletins are drafted by more than one person, often within the same hour. Content you read earlier may have moved on — a backport range added, a placeholder resolved, a section rewritten.
**Fetch the current version immediately before every write, and compare the returned version number against the one you read.** If it advanced, re-read, merge your change onto the newer content, and write that. Writing from a stale copy silently discards someone else's work with no warning and no conflict error.
After writing, diff your new version against the one you meant to build on. The diff should show only your intended change. If it shows deletions you did not intend, restore from history and redo the edit on top.
## Restrictions
Bulletins stay restricted until the coordinated publication date.
**Check restrictions before the edit and again after.** An API update should not disturb them, but "should not" is not verification, and an accidentally public pre-release bulletin is an unrecoverable disclosure.
Expected on the Struts wiki: read and update limited to the author plus `struts-committers`.
## Start from the template, never from a previous bulletin
**[`bulletin-template.md`](bulletin-template.md)** — the field reference, per-section guidance, pre-publication checklist, and a storage-format skeleton ready to POST to the Confluence API. **It is the source of truth.**
A rendered copy exists on the wiki as a restricted child of *Security Bulletins* for authors who prefer to copy a page; when the two disagree, fix the wiki page from the file. Whichever route you take, confirm the new page carries the same restrictions before typing anything into it, and give the `excerpt` macro a fresh `ac:macro-id` — a copied page inherits the template's, and two bulletins must not share one.
**If you inherit a page cloned from a previous bulletin instead**, assume every field is inherited and wrong until you have replaced it. The residue that survives a careless edit:
- The previous bulletin's real CVE identifier
- Its affected versions, rating, and reporter credit
- Its workaround — describing a mitigation for an unrelated defect
- The `excerpt` macro's `ac:macro-id`, now **duplicated across two pages** — generate a fresh UUID
Read the whole page and rewrite it; do not patch the fields you happen to notice.
## Red Flags — STOP
- About to write a Workaround you have not traced in source
- About to write "no workaround exists" without having looked
- Affected Software copied from a git tag list rather than confirmed releases
- A CVE number on the page that you did not receive from the CVE assignment process
- Naming a class, method, or file in `Problem` "because it's already public in the PR"
- Copying the disclosure depth of an older bulletin — that depth is the reason this budget exists
- Calibrating a rating against a bulletin published before the four-level scale existed
- Guarding `Problem` carefully and then describing the fix's internals in `Backward compatibility`
- Writing a BC note from the fix's commit message without reading the diff
- Publishing while the fix is still unmerged, or without re-checking that it landed
- No statement of who is *not* affected, when exposure depends on a plugin or an opt-in setting
- Writing a page from content you read earlier in the session without re-fetching it first
- Publishing without re-checking restrictions
- A severity rating chosen by feel, or by reachability alone, without checking it against the published scale
- Rating something Low because the feature is opt-in — opt-in is the definition of Moderate
## Common Mistakes
| Mistake | Reality |
|---|---|
| "The PR is public, so detail costs nothing" | A bulletin is indexed, permanent, and read by people who never see the PR. Aggregation is the harm. |
| "An older bulletin explained the cause in detail" | Those explanations were used to build exploits. The practice was stopped deliberately — don't restore it. |
| "An older bulletin rated something like this X" | The rating scale postdates it. Match a definition on page 61758 instead. |
| "Listing the failed build is more honest" | It is less accurate. That build was never a release; listing it misdirects every downstream consumer. |
| "Disabling the feature is an obvious workaround" | Obvious ≠ verified. Confirm the feature is genuinely on the only reachable path. |
| "The rating is roughly right" | Ratings drive upgrade urgency. Read the published definitions and match one, don't approximate. |
| "It needs an opt-in feature, so it's Low" | That is the Moderate mitigation clause. Low means hard to exploit or minimal consequence. |
| "Restrictions were set when the page was created" | Verify after every edit. The cost of being wrong once is total. |
| "I'll fill in the CVE later" | Only if the placeholder is unmistakable. A blank or a stale number ships as fact. |
| "The BC note is just a compatibility courtesy" | It describes what the fix changed, which describes the defect. Same budget as `Problem`. |
| "The commit message says behaviour is unchanged" | Commit summaries understate. Read the diff and decide for yourself. |
| "Naming the plugin narrows it for an attacker too" | They can read your dependency list. Scoping spares every operator who isn't exposed. |
| "The patch is reviewed, so the release will contain it" | Reviewed is not merged. Re-check at publication, not at drafting. |
| "Copying the last bulletin is quicker than the template" | It is how another advisory's CVE ships on your page. Copy the template. |
| "I read the page a few minutes ago" | Someone else may have written to it since. Re-fetch, then write. There is no conflict warning. |
@@ -1,152 +0,0 @@
# Security Bulletin Template
The canonical skeleton and per-field guidance for an S2-XXX security bulletin.
Companion to [`SKILL.md`](SKILL.md), which covers *how* to establish the facts that
go in these fields; this file covers *what the page contains*.
A rendered copy lives on the Struts wiki as a restricted child of
[Security Bulletins](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=61758)
for authors who prefer to copy a page. **This file is the source of truth** — when the
two disagree, fix the wiki page from here.
**Draft bulletins stay restricted** (read and update limited to the author plus
`struts-committers`) until the coordinated publication date. Check restrictions before
an edit and again after it: an accidentally public pre-release bulletin is an
unrecoverable disclosure.
## Fields
| Row | What goes in it |
|---|---|
| Who should read this | Usually `All Struts 2 developers and users`. Narrow it only when exposure is genuinely conditional. |
| Impact of vulnerability | A short impact phrase, not a paragraph — `Remote Code Execution`, `Denial of service`. Hedge where warranted (`Possible Remote Code Execution vulnerability`). |
| Maximum security rating | `Low` / `Moderate` / `Important` / `Critical`, matching a definition on the [Security Bulletins](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=61758) page. That page is the only authority — the four-level naming postdates many older bulletins, so never calibrate against one. |
| Recommendation | `Upgrade to Struts X.Y.Z at least`. Name **every** maintenance line carrying the fix, and add the required action where upgrading alone is not enough. |
| Affected Software | Officially released versions only. One bullet per maintenance line; link the EOL announcement for end-of-life ranges. |
| Reporters | Credit the reporter. Include their organisation where they gave one; obfuscate any email address. |
| CVE Identifier | `CVE-YYYY-NNNNN (to be assigned before publication)` until the real identifier arrives. One CVE per independently fixable issue. |
### Affected Software
List only versions that passed a PMC release vote. A build that was cut, failed its
test period and was superseded never reached users — listing it implies an official
artifact was vulnerable and drags a phantom version into every downstream CVE record
and scanner database. Do not read the range off git tags; tags exist for builds that
were never voted through.
To find the lower bound: locate when the vulnerable construct entered with
`git log -S`, then map that commit to the first release containing it. A defect often
predates the control that was supposed to bound it, so do not assume it arrived with
the feature that made it reachable. If the mapping cannot be settled, write a visible
placeholder naming what must be confirmed — never a guessed version number.
## Problem
One to three sentences. Write for an operator, not a reviewer: the feature, the
failure, the consequence.
| Safe to publish | Never publish before the fix is out |
|---|---|
| Impact categories and consequence | Class, method, or field names |
| The component in plain words, linked to its documentation | `file:line` references |
| Whether a configured control fails to apply | Commit hashes, PR or Jira numbers |
| That state is shared / input is unvalidated | The triggering request shape or payload |
| Which released versions are affected | Reproduction steps, PoC, timing conditions |
Older bulletins explained causes and mitigations in far more detail, and that detail
was used to build working exploits. The project deliberately stopped. **Mine the
archive for structure and tone, never for depth.**
**Then say who is not affected.** An operator's first question is "does this reach
me?" — answer it, or every reader must assume it does. One sentence, linked to the
feature's documentation, either as a trailing note or folded into the opening clause.
Name the optional plugin, the setting that must be switched on, the endpoint that must
be mapped, or the unaffected sibling path. Add "earlier releases are not affected"
where there is a clean prior baseline.
Keep it at the level of a deployment decision, not a code path. Scoping *reduces* net
disclosure: it shrinks the population that has to care, and costs an attacker nothing
they could not read off a dependency list.
## Solution
`Upgrade to Struts X.Y.Z at least.` Repeat for each maintenance line, and link the
migration guide where the fix requires one.
## Backward compatibility
**Subject to the same disclosure budget as Problem.** This is the section that leaks:
describing what changed about the fixed behaviour describes the defect. Write it in
terms of what an application might *observe*, never what the fix altered internally,
and derive it from the fix diff rather than the commit message — a summary calling the
behaviour unchanged can still carry an observable difference.
It is also where a **breaking** upgrade is announced, and that announcement has to be
blunt: what must be rewritten, and what staying put costs. Where the fix is
transparent, the house sentence is simply `This change is backward compatible.`
## Workaround
Three valid outcomes, in order of preference:
1. **A verified configuration or operational change** — traced in source and confirmed
to remove reachability. Give the change, not the mechanism. It need not be a Struts
setting; container and reverse-proxy limits count, as does pointing at the relevant
section of the Security Guide.
2. **Upgrade only**, when you checked and found nothing.
3. **Verified absence.** The house value is a bare `n/a`; spell it out when the reason
is worth stating.
Never ship a workaround you reasoned about but did not confirm — it leaves operators
believing they are protected and discredits every other field on the page. "No
workaround exists" is a claim about absence and needs checking too.
A workaround usually reveals which path is affected. That is often the right trade,
but make it deliberately and record which way you went.
## Before publishing
- [ ] Every placeholder is replaced, and no guidance text survives on the page.
- [ ] The CVE identifier is real, not the placeholder.
- [ ] Affected Software lists voted releases only, and covers every maintenance line.
- [ ] The rating matches a published definition rather than an approximation.
- [ ] The workaround was verified in source, or its absence was.
- [ ] Problem, Backward compatibility and Workaround name no class, file, commit, PR
or payload.
- [ ] The fix is **merged** into the release branch — reviewed is not merged; re-check
now, not at drafting time.
- [ ] The fixed release is out and accepted.
- [ ] Restrictions are lifted only at the coordinated publication moment.
## Storage-format skeleton
Ready to POST to the Confluence API. Give the `excerpt` macro a **fresh**
`ac:macro-id` each time — two bulletins must not share one.
```xml
<h2>Summary</h2>
<ac:structured-macro ac:name="excerpt" ac:schema-version="1">
<ac:parameter ac:name="atlassian-macro-output-type">BLOCK</ac:parameter>
<ac:rich-text-body><p>ONE-LINE DESCRIPTION OF THE DEFECT</p></ac:rich-text-body>
</ac:structured-macro>
<p class="auto-cursor-target"><br/></p>
<table class="wrapped"><colgroup><col/><col/></colgroup><tbody>
<tr><th><p>Who should read this</p></th><td><p>All Struts 2 developers and users</p></td></tr>
<tr><th><p>Impact of vulnerability</p></th><td><p>IMPACT PHRASE</p></td></tr>
<tr><th><p>Maximum security rating</p></th><td><p>Low | Moderate | Important | Critical</p></td></tr>
<tr><th><p>Recommendation</p></th><td><p>Upgrade to Struts X.Y.Z at least</p></td></tr>
<tr><th><p>Affected Software</p></th><td><ul style="list-style-type: square;">
<li>Struts A.B.C through Struts D.E.F</li></ul></td></tr>
<tr><th><p>Reporters</p></th><td><p>REPORTER</p></td></tr>
<tr><th><p>CVE Identifier</p></th><td><p>CVE-YYYY-NNNNN (to be assigned before publication)</p></td></tr>
</tbody></table>
<h2>Problem</h2>
<p>WHAT THE DEFECT ALLOWS, IN OPERATOR TERMS.</p>
<p>WHO IS NOT AFFECTED, AND WHY.</p>
<h2>Solution</h2>
<p>Upgrade to Struts X.Y.Z at least.</p>
<h2>Backward compatibility</h2>
<p>This change is backward compatible.</p>
<h2>Workaround</h2>
<p>WORKAROUND, OR A STATEMENT THAT NONE EXISTS.</p>
```
@@ -1,198 +0,0 @@
---
name: creating-version-notes
description: Use when preparing, updating, or reviewing the release documentation for a Struts release or release candidate on any maintenance line (6.x, 7.x) - the Version Notes page on the cwiki, its Migration Guide entry, and the GitHub release notes.
---
# Creating Version Notes
## Overview
A Version Notes page answers one question for a user deciding whether to upgrade: **what changed in this release, and what will break.** Almost all of it is a mechanical rendering of a JIRA fix version onto fixed boilerplate.
**Core principle:** the mechanical parts must be *derived*, never retyped; the two judgement parts — Breaking changes, and how a security fix is described — are the only places you author prose.
**One skill covers every maintenance line.** 6.x and 7.x pages share an identical structure. The line changes the data (version, prior page, JIRA ids), never the process.
## The Iron Rule
```
START FROM THE TEMPLATE. NEVER CLONE THE PREVIOUS VERSION NOTES PAGE.
```
Cloning is how the published pages acquired their defects, and it fails differently every time:
| Page | Inherited defect |
|---|---|
| Version Notes 6.9.0 | Issue Detail links **"JIRA Release Notes 6.8.0"** — label and `version=` id both from 6.8.0 |
| Version Notes 6.10.0 | Issue List links **"Struts 6.9.0 DONE"** — label names the previous release, against a `filter=` id different from the one the 6.9.0 page used |
| Both series | Maven Dependency code macro carries `ac:name=""` instead of `ac:name="language"` |
Half-updated links are the signature failure: the number gets fixed and the label doesn't, or the reverse. They survive review because the link still works — it just points at, or claims to be, the wrong release.
**[`version-notes-template.md`](version-notes-template.md) is the source of truth**: field guidance, storage-format skeleton with those defects corrected, and the pre-publication checklist.
## Collect every input before writing
Each row is derived from a named source. A value you cannot source is a visible placeholder, never a guess.
| Input | Where it comes from |
|---|---|
| Version | The release being voted or announced |
| Parent page | Always **Migration Guide** (page id `13981`) — every Version Notes page is a child of it |
| Prior notes page title | The previous **released** version in the same series — see below |
| JIRA version id | Numeric id behind `ReleaseNote.jspa?version=` — from the WW project's versions, **not** the version name |
| DONE filter id | The saved JIRA filter for this release; a new release needs a new filter |
| Issue list | `project = WW AND fixVersion = <version>`, grouped by type |
| Breaking changes | Authored — see below |
| Staging Repository block | An explicit decision — see below |
## The issue list
Group under `<h2>` per issue type, in this order, omitting any type with no issues:
**Bug → New Feature → Improvement → Task → Dependency**
Within a section, order by issue key ascending. Each entry is `[WW-XXXX] - <the JIRA summary verbatim>`.
**Reconcile against what actually merged.** The JIRA query is the starting point, not the answer. Two mismatches to check:
- A ticket marked fixed whose change did not make the release branch — it must not be listed.
- Work that shipped under a ticket assigned to a different fix version — the notes under-report the release.
**Reconcile through the ticket's linked PR, reading the files it changed.** Do not grep commit subjects, and do not go looking for the class named in the ticket title: a title often names the *symptom* while the fix lives elsewhere. WW-5630 reads "Performance Issue SecurityMemberAccess" and was fixed in `ConfigParseUtil`; searching for the former concludes, wrongly, that the backport is missing. Squash-merges also rewrite hashes, so the merge commit id from the PR need not appear on the branch.
**Untick eted patch-level dependency bumps are not a gap.** Dependabot PRs for patch updates are merged directly and deliberately get no ticket, so they get no entry — there is nothing to link. Expect the pom to show a higher patch version than the ticket text says: 6.11.0 shipped jackson 2.22.1 while WW-5648 reads "2.21.4 to 2.22.0". That is correct, not an omission. Minor and major bumps do get a ticket and do get listed.
Where a ticket's summary was written for triage rather than for users, the page may carry a clearer summary — but then it is authored text, and the link must still resolve to that ticket.
## Only released versions belong in the chain
The prior-notes link forms a chain through the series, and it **skips versions that were cut but never released**. Version Notes 7.2.1 links back to 7.1.1, not to the withdrawn 7.2.0.
When a release is superseded before it ships, its content does not disappear — the successor absorbs it. 7.2.1 carries the Breaking changes for the whole 7.2.x cycle. Check what the predecessor covered before assuming your issue list is complete.
This is the same discipline `creating-security-bulletins` applies to Affected Software, for the same reason: naming a version that never reached users misdirects everyone downstream.
## Breaking changes
Present only when the release has them — a maintenance release usually does not. This section is **authored prose, not a ticket dump**: one item per change, each stating what an application must now do differently, with its ticket(s) linked at the end.
The register is the upgrade decision, not the implementation. From 7.2.1:
> `CookieInterceptor` now applies `@StrutsParameter` authorization to cookie values and deprecates the 4-arg `populateCookieValueIntoStack(...)` in favor of a new 5-arg overload taking the action, so un-annotated setters stop receiving cookies and subclass overrides must migrate.
Name the type or setting a user must act on, say what stops working, and say what replaces it.
## Security fixes in a release
A release usually ships before its bulletin publishes and before a CVE exists. The Version Notes then list a **public, neutrally-framed** ticket for a defect whose advisory is still restricted.
- List the ticket as you would any other. It is already public; omitting it under-reports the release.
- **Do not add security framing the bulletin has not published yet** — no severity, no attack description, no S2-XXX or CVE number that has not been assigned and published.
- Once the bulletin is public, the notes may link it.
**REQUIRED BACKGROUND:** where the wording of a security-relevant entry is in question, `creating-security-bulletins` governs what may be said and when.
## The Staging Repository block
**Include it.** The block points readers at ASF Nexus staging so they can test the artifacts before the vote closes, and it stays on the page afterwards.
Older 6.x pages (6.9.0, 6.10.0) lack it while the 7.x pages carry it. That is an artefact of cloning within each series, not a difference between the lines — 6.11.0 carries it.
## Link the new page from the Migration Guide
The page is not finished when it is created. **[Migration Guide](https://cwiki.apache.org/confluence/spaces/WW/pages/13981/Migration+Guide) (id `13981`) is both the parent page and the index**, and a Version Notes page that is not listed there is unreachable by anyone browsing.
Add an entry at the **top** of the list under the `<h2>` for the matching line — `Version Notes 7.x`, `Version Notes 6.x`, and so on. The lists are newest-first, and the entry is a page link carrying no body text:
```xml
<li><ac:link><ri:page ri:content-title="Version Notes X.Y.Z"/></ac:link></li>
```
**Update the section, not the whole page.** `confluence_update_page_section` on the exact heading replaces only that section's body; its boundary is the next `<h2>`, so the section body includes the `<h3>` migration-guide link that follows the list. Supply that `<h3>` and its paragraph in the replacement content or they are dropped.
**Verify against raw storage, not the diff.** A version diff of this page renders empty even for a real change, because the markdown view discards `ac:link` bodies. Fetch the new version with `convert_to_markdown=false` and confirm the new entry is present, the prior entries survive in order, and the trailing `<h3>` appears exactly once.
## The GitHub release notes
A release also has a GitHub release at the `STRUTS_X_Y_Z` tag, kept as a **pre-release** while the vote runs. GitHub's generated body is a starting point that needs two corrections before it is fit to publish.
### Check the range before anything else
The generated body ends with `**Full Changelog**: .../compare/<PREVIOUS>...<THIS>`. **Confirm `<PREVIOUS>` is the immediately preceding release on this line.** GitHub picks it by tag reachability, and Struts release branches get renamed and re-imported, so older tags are frequently *not* ancestors of the new one and the heuristic reaches too far back.
For 6.11.0 it chose `STRUTS_6_8_0` and produced ~101 entries, 88 of which had already shipped in 6.9.0 and 6.10.0.
Get the real change set from git, which works even across unrelated histories:
```bash
git log --format='%h %s' STRUTS_6_10_0..STRUTS_6_11_0
```
Drop every generated entry outside that range and correct the Full Changelog link to the right previous tag. Drop `## New Contributors` too when the contribution it cites falls outside the range.
### Split the entries
Two sections, `### Dependencies` nested under `## What's Changed`, before any `## New Contributors`:
| Entry | Section |
|---|---|
| Carries a `WW-XXXX` ticket — whoever authored it | `## What's Changed` |
| A human PR that is not a dependency change (ci, chore, release prep) | `## What's Changed` |
| A dependency bump with **no** ticket | `### Dependencies` |
**The discriminator is the ticket, not the author.** A Dependabot PR carrying a ticket stays in What's Changed, because a ticketed bump is release content and appears in the Version Notes Dependency section. A human PR that is purely a dependency change (`Removes unused jaxb-core dependency`) belongs under Dependencies. Both cases occur in the 6.9.0 release.
Preserve the generated relative order within each section, and keep the entry lines byte-identical — they carry the author and PR links GitHub rendered.
### Applying it
```bash
gh release view STRUTS_X_Y_Z --json body -q .body > original.md # keep, so it can be restored
gh release edit STRUTS_X_Y_Z --prerelease --notes-file new.md
```
Pass `--prerelease` on the edit so a release still under vote is not silently promoted.
## Re-read the page immediately before you write to it
Confluence has no conflict warning. Fetch the current version immediately before every write and compare the version number against the one you read; if it advanced, re-read, merge onto the newer content, and write that.
After writing, diff against the version you meant to build on. The diff should show only your intended change.
## Red Flags — STOP
- Starting from a copy of the previous release's page
- A version number or JIRA id typed rather than derived
- A link whose label and its id name different releases
- The prior-notes link pointing at a version that was cut but never released
- Publishing the issue list straight from JIRA without reconciling against the release branch
- Concluding a backport is missing from a commit-subject grep, or from the class named in the ticket title
- Treating an untick eted patch dependency bump as a reconciliation gap
- A severity, CVE, or S2-XXX reference on the page that has not been published
- Breaking changes assembled by pasting ticket summaries
- Creating the page without adding it to the Migration Guide index
- Trusting an empty version diff on the Migration Guide as proof the edit landed
- Publishing GitHub release notes without checking which tag the Full Changelog compares against
- Splitting the GitHub sections by author instead of by whether the entry carries a ticket
- Editing a GitHub release under vote without `--prerelease`
- Writing from page content read earlier in the session without re-fetching
## Common Mistakes
| Mistake | Reality |
|---|---|
| "Copying last release's page is faster" | It is how "JIRA Release Notes 6.8.0" shipped on the 6.9.0 page. Copy the template. |
| "I updated the link, it's fine" | Check the label too. Every observed defect is a half-updated link. |
| "`version=` takes the version number" | It takes JIRA's numeric version id. Look it up. |
| "The DONE filter can be reused" | A reused filter shows the previous release's issues under this release's heading. |
| "JIRA is the release contents" | JIRA is the claim. The release branch is the fact. Reconcile. |
| "No commit mentions the ticket, so it wasn't backported" | Read the linked PR's changed files. Titles name symptoms, and squash-merges rewrite hashes. |
| "The pom version doesn't match the ticket, that's a gap" | Patch bumps ship untick eted by design. Only ticketed bumps get an entry. |
| "The page is created, so the work is done" | It is invisible until listed on the Migration Guide. |
| "The version diff is empty, so nothing changed" | The diff renders markdown, which drops `ac:link` bodies. Check raw storage. |
| "GitHub generated the changelog, so the range is right" | It guesses the previous tag by reachability. Renamed branches make it reach too far back. Verify with `git log PREV..THIS`. |
| "Dependabot authored it, so it goes under Dependencies" | Ticketed bumps stay in What's Changed. The ticket decides, not the author. |
| "The fix is public, so I can describe the vulnerability" | The ticket being public does not publish the advisory. Neutral framing until the bulletin ships. |
| "Breaking changes are the tickets typed as breaking" | They are the changes that break an application. Author them. |
| "7.x needs different handling from 6.x" | Same structure, same process. Only the data differs. |
@@ -1,127 +0,0 @@
# Version Notes Template
The canonical skeleton and per-field guidance for a Struts **Version Notes X.Y.Z** page
on the [Apache Struts 2 Wiki](https://cwiki.apache.org/confluence/spaces/WW) (space `WW`).
Companion to [`SKILL.md`](SKILL.md), which covers *how* to establish the values;
this file covers *what the page contains*.
**This file is the source of truth.** Start every page from the skeleton below, never
from a copy of the previous release's page — see the Iron Rule in `SKILL.md`.
## Fields
| Field | What goes in it |
|---|---|
| Version | The release being announced, e.g. `6.11.0`. Appears in the intro sentence, the page title, the Maven snippet, and both JIRA link labels. |
| Parent page | Always `Migration Guide`, page id `13981`. Create the page as its child, and add it to that page's index — see `SKILL.md`. |
| Prior notes page | Title of the previous **released** version's page in the same series, e.g. `Version Notes 6.10.0`. Skip versions that were cut but never released. |
| JIRA version id | The numeric id for `ReleaseNote.jspa?version=`. Obtain from the WW project's versions — it is not the version name. `6.10.0` is `12357065`, `7.2.1` is `12355751`. |
| DONE filter id | Saved-filter id for `issues/?filter=`, labelled `Struts X.Y.Z DONE`. Each release needs its own; a reused id lists the wrong release. |
| TODO filter id | Constant across releases: `12351174`, labelled `Struts x.x.x TODO`. |
| Issue sections | One `<h2>` per issue type present, ordered **Bug → New Feature → Improvement → Task → Dependency**, entries sorted by key ascending. |
| Breaking changes | Optional. Authored prose, one `<li>` per change. Omit the section entirely when the release has none. |
| Staging Repository | Always included, on every line — see `SKILL.md`. |
## Corrected storage format
Three defects present in the published pages are fixed here. Keep them fixed:
1. **`ac:name="language"` on the code macros.** The published Maven Dependency and
Staging Repository macros carry `ac:name=""` with the value `xml`, which is a
malformed parameter. The Archetype Catalog macro on the same pages has it right.
2. **No `ac:macro-id` attributes.** The published pages share hard-coded macro ids
across releases and across series because they were cloned. Omit the attribute and
let Confluence assign one on save.
3. **No trailing empty `<div>`s.** Every published page ends with two empty divs
carrying inline `font-size: 24.0px` styling. They render as stray whitespace.
```xml
<p><ac:emoticon ac:name="tick"/> These are the notes for the Struts version X.Y.Z distribution.</p>
<p><ac:emoticon ac:name="tick"/> For prior notes in this release series, see <ac:link><ri:page ri:content-title="Version Notes PRIOR"/></ac:link></p>
<p><ac:structured-macro ac:name="toc" ac:schema-version="1"/></p>
<h2>Maven users</h2>
<p>If you are a Maven user, you might want to get started using the <ac:link><ri:page ri:content-title="Struts 2 Maven Archetypes"/><ac:plain-text-link-body><![CDATA[Maven Archetype]]></ac:plain-text-link-body></ac:link>.</p>
<ac:structured-macro ac:name="code" ac:schema-version="1">
<ac:parameter ac:name="title">Maven Dependency</ac:parameter>
<ac:parameter ac:name="language">xml</ac:parameter>
<ac:plain-text-body><![CDATA[<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>X.Y.Z</version>
</dependency>
]]></ac:plain-text-body>
</ac:structured-macro>
<p>You can also use Struts Archetype Catalog like below</p>
<ac:structured-macro ac:name="code" ac:schema-version="1">
<ac:parameter ac:name="language">text</ac:parameter>
<ac:parameter ac:name="title">Struts Archetype Catalog</ac:parameter>
<ac:plain-text-body><![CDATA[mvn archetype:generate -DarchetypeCatalog=http://struts.apache.org/]]></ac:plain-text-body>
</ac:structured-macro>
<ac:structured-macro ac:name="code" ac:schema-version="1">
<ac:parameter ac:name="title">Staging Repository</ac:parameter>
<ac:parameter ac:name="language">xml</ac:parameter>
<ac:plain-text-body><![CDATA[<repositories>
<repository>
<id>apache.nexus</id>
<name>ASF Nexus Staging</name>
<url>https://repository.apache.org/content/groups/staging/</url>
</repository>
</repositories>]]></ac:plain-text-body>
</ac:structured-macro>
<!-- OPTIONAL: omit the whole section when the release has no breaking changes -->
<h2>Breaking changes</h2>
<ul style="list-style-type: square;">
<li>WHAT AN APPLICATION MUST NOW DO DIFFERENTLY, AND WHAT REPLACES THE OLD BEHAVIOUR [<a href="https://issues.apache.org/jira/browse/WW-XXXX">WW-XXXX</a>].</li>
</ul>
<h2>Bug</h2>
<ul><li>[<a href="https://issues.apache.org/jira/browse/WW-XXXX">WW-XXXX</a>] - JIRA SUMMARY</li></ul>
<h2>Issue Detail</h2>
<ul><li><a href="https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=12311041&amp;version=JIRA_VERSION_ID">JIRA Release Notes X.Y.Z</a></li></ul>
<h2>Issue List</h2>
<ul>
<li><a href="https://issues.apache.org/jira/issues/?filter=DONE_FILTER_ID">Struts X.Y.Z DONE</a></li>
<li><a href="https://issues.apache.org/jira/issues/?filter=12351174">Struts x.x.x TODO</a></li>
</ul>
<h2>Other resources</h2>
<ul>
<li><a href="http://www.mail-archive.com/commits%40struts.apache.org/">Commit Logs</a></li>
<li><a href="https://gitbox.apache.org/repos/asf?p=struts.git;a=summary">Source Code Repository</a></li>
</ul>
```
Repeat the issue `<h2>` block per type present, in the order given above.
`projectId=12311041` is the WW project and is constant. Note `&amp;` in the
`ReleaseNote.jspa` URL — a bare `&` is invalid in storage format.
## Before publishing
- [ ] Every placeholder is replaced, and no guidance text survives on the page.
- [ ] Page title is `Version Notes X.Y.Z` and the intro names the same version.
- [ ] Prior-notes link resolves, and names the previous **released** version.
- [ ] Maven snippet version matches the release.
- [ ] `ReleaseNote.jspa` label and its `version=` id are the same release.
- [ ] `DONE` filter label and its `filter=` id are the same release.
- [ ] Issue list reconciled against the release branch via each ticket's linked PR, not taken from JIRA alone.
- [ ] Issue types ordered Bug → New Feature → Improvement → Task → Dependency; empty types omitted.
- [ ] Breaking changes authored, or the section omitted because there are none.
- [ ] Staging Repository block present.
- [ ] No unpublished severity, CVE, or S2-XXX reference anywhere on the page.
- [ ] Page created as a child of Migration Guide (`13981`).
- [ ] **Listed at the top of the matching `Version Notes N.x` section on the Migration Guide**, and that edit verified against raw storage — the version diff renders empty even when the change landed.
- [ ] Page re-fetched immediately before every write.
## GitHub release notes
- [ ] Original generated body saved before editing, so it can be restored.
- [ ] Full Changelog compares against the **immediately preceding release** on this line, verified with `git log PREV..THIS` — GitHub's guess is often wrong after a branch rename.
- [ ] Entries outside that range removed, including a `## New Contributors` block citing one.
- [ ] Entries split by **ticket, not author**: ticketed → `## What's Changed`; untick eted dependency bumps → `### Dependencies`.
- [ ] Generated order and entry text preserved within each section.
- [ ] `gh release edit` passed `--prerelease` while the vote is open.
@@ -1,98 +0,0 @@
---
name: triaging-security-reports
description: Use when a vulnerability or security report arrives for triage, when assessing a CVE/RCE/OGNL/injection claim against the code, or when drafting a reply to a security researcher — to research the claim from source without trusting the reporter and without fabricating your own facts.
---
# Triaging Security Reports
## Overview
A security report is a **claim to be tested, not a finding to be confirmed or rebutted**. The reporter may be right, wrong, partially right, or right about the symptom and wrong about the cause. Your job is to independently re-derive the truth from current source.
**Core principle:** Every factual statement that ends up in your assessment or reply — the reporter's claims *and your own* — must be traced to current source code before you write it down. The most common failure is not believing the reporter; it is **inventing supporting facts to justify a verdict you already reached.**
**Process authority:** [`SECURITY.md`](../../../SECURITY.md) is the source of truth for the disclosure process (private handling, assessment checklist, reporting rules). Read it. This skill governs *how you research and respond*, not the process itself.
## The Iron Rule
```
NO CLAIM IN A SECURITY RESPONSE WITHOUT A FILE:LINE YOU READ THIS SESSION.
```
Applies to the verdict, every mitigation you cite, and every "default" you state. If you can't point to the line, you can't write the sentence.
## Research: report-blind, not report-led
Read the report once to know what to investigate. Then **research as if you were auditing that area cold** — do not let the report's framing drive your search.
For each claim, independently verify:
| Reporter asserts | You must verify from source |
|---|---|
| A line number ("bug is at X:392") | Read that line **and its call path** — is it even reachable as described? |
| A severity / CVSS | Re-derive from actual exploitability, not their number |
| "No mitigation / no gate exists" | Search for gates, filters, allowlists, authorizers *yourself* — absence claims are the most often wrong |
| "Default configuration" | Check the **effective runtime default**, not one source (see trap below) |
| "Same as CVE-XXXX" | Confirm the mechanism actually matches; analogy ≠ equivalence |
| A working PoC | **Run it if it is runnable**, then trace whether the payload survives every filter on the path |
If the report has **no reproducible PoC against a default config**, that is itself a triage outcome — say so per `SECURITY.md`.
## Find the control case
A single odd behaviour is ambiguous — it can nearly always be read as intended. What settles it is the **sibling that behaves correctly under the same input**.
Before writing a verdict, find the case that ought to differ and check it: the annotated property beside the unannotated one, the ordinary setter beside the dynamic one, the sibling path the same control does cover. Behave alike and you are probably looking at a design decision. Diverge, and the control is incomplete — that divergence *is* the finding.
Prefer an executed differential to an argued one. An existing test that passes beside the reporter's failing one is the strongest evidence a triage can produce.
## The effective-default trap
A Java field initializer and the shipped config can disagree. Reading only one produces a confident, wrong claim.
```java
private boolean requireAnnotations = false; // field initializer
```
```properties
struts.parameters.requireAnnotations=true # default.properties OVERRIDES it
```
**The effective default is `true`.** Always trace the full chain: field initializer → `@Inject` setter → `default.properties` → any struts.xml override. State the *effective runtime* value, and cite the file that actually wins.
## Vulnerability vs. operator responsibility
"In the default configuration" is a crutch — drop it. Decide the real question:
- **Is it a vulnerability?** Then it's a vulnerability whether or not it's the default. Handle it privately per `SECURITY.md`.
- **Does it require an operator to opt into an insecure configuration?** A documented, opt-in setting (e.g. `cookiesName=*`, `devMode=true`) that works as advertised is the operator's responsibility, provided the docs carry the warning. Say "X works as documented; the operator owns the security implications of enabling it" — not "not a vuln *in the default config*."
- **Is the RCE/escalation only reachable via application code the framework can't constrain?** (e.g. an action that moves an uploaded file to a web root.) Then it's an application concern, not a framework vulnerability — state that boundary explicitly.
## Drafting the reply
- Lead with the verdict and the *reason*, both grounded in file:line.
- Cite a source for every mitigation you mention. If you didn't verify it this session, delete the sentence.
- Prefer "works as documented / operator responsibility" framing over "default configuration."
- **Don't over-promise.** Before pledging a hardening change, check it doesn't already exist (it often does) and that you intend to actually do it.
- Acknowledge anything the reporter got right (e.g. correct CVE-fix verification) — it builds the relationship and signals you actually read it.
- Keep it private: no public issue, PR, Jira, or list thread before triage. Never open a PR that is itself the security fix (see [`CLAUDE.md`](../../../CLAUDE.md)).
## Red Flags — STOP
- About to write "this is mitigated by X" — did you read X's line *this session*?
- About to state a "default" from a field initializer — did you check `default.properties`?
- Citing the reporter's line number without having traced its call path.
- Asserting "no gate / no check exists" without having grepped for it.
- Two of your own claims contradict each other → at least one is unverified. Stop and verify both.
- Promising a fix/warning "we'll add" without checking it isn't already there.
- Writing "not a vulnerability in the default configuration" → reframe as vuln-or-not + operator responsibility.
## Common Mistakes
| Mistake | Reality |
|---|---|
| "Reporter cited line 392, so that's the bug site" | A line is only a bug if it's *reachable* as described. Trace callers. |
| "The field defaults to false, so the gate is off by default" | `default.properties` may override it to true. Check the effective value. |
| "I'll add a mitigation to strengthen the rejection" | An unverified mitigation that's wrong discredits the whole response. Verify or omit. |
| "It rejects the payload, obviously" | Confirm the specific PoC string fails the specific filter (e.g. full-match regex `ACCEPTED_PATTERN`). |
| "We should add a startup warning" | Grep first — the warning frequently already exists. |
| "Not a vuln in default config" | Either it's a vuln or it's operator-owned opt-in. The default-config hedge muddies both. |
+1 -27
View File
@@ -8,35 +8,9 @@ updates:
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 3
target-branch: "main"
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 4
target-branch: "support/struts-6-x-x"
ignore:
- dependency-name: "org.eclipse.jetty:jetty-maven-plugin"
- dependency-name: "com.github.ben-manes.caffeine:caffeine"
- dependency-name: "com.sun.xml.bind:jaxb-impl"
- dependency-name: "javax.portlet:portlet-api"
- dependency-name: "javax.servlet:javax.servlet-api"
- dependency-name: "javax.servlet.jsp:jsp-api"
- dependency-name: "org.mortbay.jetty:jsp-2.1"
- dependency-name: "ognl:ognl"
- dependency-name: "org.hibernate.validator:hibernate-validator"
- dependency-name: "org.testng:testng"
- dependency-name: "org.mockito:mockito-core"
- dependency-name: "opensymphony:sitemesh"
- dependency-name: "net.sf.jasperreports:jasperreports"
- dependency-name: "javax.enterprise:cdi-api"
- dependency-name: "org.springframework:*"
- dependency-name: "org.apache.struts:struts-annotations"
- dependency-name: "org.apache.juneau:juneau-marshall"
- dependency-name: "org.apache.tomcat:tomcat-api"
- dependency-name: "org.apache.tomcat:tomcat-juli"
- dependency-name: "org.apache.tomcat:tomcat-jasper"
- dependency-name: "org.apache.rat:apache-rat-plugin"
target-branch: "release/struts-6-7-x"
+4 -5
View File
@@ -20,7 +20,6 @@ on:
branches:
- 'main'
- 'release/*'
- 'support/*'
pull_request:
permissions:
@@ -45,7 +44,7 @@ jobs:
language: [ 'java' ]
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v5
- name: Setup Java JDK
uses: actions/setup-java@v5
with:
@@ -53,12 +52,12 @@ jobs:
java-version: 17
cache: 'maven'
- name: Initialize CodeQL
uses: github/codeql-action/init@v4.37.3
uses: github/codeql-action/init@v3.30.5
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v4.37.3
uses: github/codeql-action/autobuild@v3.30.5
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4.37.3
uses: github/codeql-action/analyze@v3.30.5
with:
category: "/language:${{matrix.language}}"
+3 -23
View File
@@ -20,11 +20,7 @@ on:
push:
branches:
- 'main'
- 'develop'
- 'release/*'
- 'support/*'
workflow_dispatch:
workflow_call:
permissions: read-all
@@ -34,10 +30,9 @@ env:
jobs:
build:
name: Build and Test (JDK ${{ matrix.java }})${{ matrix.profile == '-Pjakartaee11' && ' (Jakarta EE 11 + Spring 7)' || matrix.profile }}
name: Build and Test (JDK ${{ matrix.java }})${{ matrix.profile == '-Pjakartaee11' && ' with Jakarta EE 11' || matrix.profile }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- java: '17'
@@ -46,29 +41,14 @@ jobs:
profile: ''
- java: '21'
profile: '-Pjakartaee11'
- java: '25'
profile: ''
- java: '25'
profile: '-Pjakartaee11'
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v5
- name: Setup Java ${{ matrix.java }}
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: 'maven'
- name: Maven Verify on Java ${{ matrix.java }}${{ matrix.profile == '-Pjakartaee11' && ' (Jakarta EE 11 + Spring 7)' || matrix.profile }}
- name: Maven Verify on Java ${{ matrix.java }}${{ matrix.profile == '-Pjakartaee11' && ' (Jakarta EE 11)' || matrix.profile }}
run: mvn -B -V -DskipAssembly verify ${{ matrix.profile }} --no-transfer-progress
- name: Test Summary ${{ matrix.java }} ${{ matrix.profile }}
uses: mikepenz/action-junit-report@d9f48fc87bc235f7e214acf696ca5abc0a986f16 #v6.4.2
continue-on-error: true
if: always()
with:
annotate_only: true # forked repo cannot write to checks so just do annotations
report_paths: |
**/surefire-reports/TEST-*.xml
**/failsafe-reports/TEST-*.xml
-100
View File
@@ -1,100 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: OWASP checkup
on:
pull_request:
push:
branches:
- 'main'
- 'develop'
- 'release/*'
- 'support/*'
workflow_dispatch: #Allow manual triggers
permissions: read-all
env:
MAVEN_OPTS: -Xmx2048m -Xms1024m
LANG: en_US.utf8
jobs:
owasp:
name: OWASP
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HAVE_NIST_NVD_API_KEY: ${{ secrets.NIST_NVD_API_KEY != '' }}
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Setup Java 25
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 25
cache: 'maven'
- name: Cache NVD Database
id: cache-nvd
uses: actions/cache/restore@v6
with:
path: ~/.m2/repository/org/owasp/dependency-check-data
key: nvd-cache-${{ runner.os }}-owasp-${{ github.run_id }}
restore-keys: |
nvd-cache-${{ runner.os }}-owasp-
nvd-cache-${{ runner.os }}-
- name: OWASP Dependency check update cache via NIST_NVD_API_KEY
id: nvd-api-update
if: ${{ env.HAVE_NIST_NVD_API_KEY == 'true' }}
continue-on-error: true
run: mvn -N -V -DskipAssembly -Dmaven.test.skip=true -Powasp-nvd-api -Pdependency-update-only --no-transfer-progress
env:
NIST_NVD_API_KEY: ${{ secrets.NIST_NVD_API_KEY}}
- name: OWASP Dependency check update cache via Mirror
if: ${{ env.HAVE_NIST_NVD_API_KEY == 'false' || steps.nvd-api-update.outcome == 'failure' }}
run: mvn -N -V -DskipAssembly -Dmaven.test.skip=true -Powasp-nvd-mirror -Pdependency-update-only --no-transfer-progress
- name: Cache NVD Database
uses: actions/cache/save@v6
if: ${{ always() }}
with:
path: ~/.m2/repository/org/owasp/dependency-check-data
key: nvd-cache-${{ runner.os }}-owasp-${{ github.run_id }}
- name: OWASP check (Without running tests)
run: mvn -B org.owasp:dependency-check-maven:aggregate -Pdependency-check -Pjakartaee11 -DautoUpdate=false --no-transfer-progress
- name: Upload Dependency Check reports
uses: actions/upload-artifact@v7
if: always()
with:
name: dependency-check
path: target/dependency-check*
- name: Add OWASP summary
if: always()
run: |
{
echo "## OWASP Dependency Check"
echo ""
echo "The HTML report has been uploaded as the **dependency-check** artifact."
echo "Download it from the Artifacts section of this workflow run."
} >> "$GITHUB_STEP_SUMMARY"
+4 -4
View File
@@ -41,12 +41,12 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@v7 # 3.1.0
uses: actions/checkout@v5 # 3.1.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # 2.4.4
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # 2.4.2
with:
results_file: results.sarif
results_format: sarif
@@ -58,13 +58,13 @@ jobs:
publish_results: true
- name: "Upload artifact"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 7.0.1
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@f58f0d11ebf5dedd870fab2f999275f7602cfa46 # 2.22.11
uses: github/codeql-action/upload-sarif@6a87ebe42bbd3423c818b3d15ce9803ba45bd522 # 2.22.11
with:
sarif_file: results.sarif
+2 -10
View File
@@ -26,7 +26,6 @@ permissions: read-all
env:
MAVEN_OPTS: -Xmx2048m -Xms1024m
LANG: en_US.utf8
HAVE_SONARCLOUD_TOKEN: ${{ secrets.SONARCLOUD_TOKEN != '' }}
jobs:
sonarcloud:
@@ -34,7 +33,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ !github.event.pull_request.base.repo.fork && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-java@v5
@@ -42,14 +41,7 @@ jobs:
distribution: temurin
java-version: 21
cache: 'maven'
- name: SonarCloud Scan
if: ${{ env.HAVE_SONARCLOUD_TOKEN == 'true' }}
env:
- env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
run: ./mvnw -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Pcoverage -DskipAssembly
- name: SonarCloud Scan -- SKIPPED
if: ${{ env.HAVE_SONARCLOUD_TOKEN != 'true' }}
run: |
echo "### SonarCloud not configured" >> $GITHUB_STEP_SUMMARY
echo "secrets.SONARCLOUD_TOKEN not existing, cannot push coverage checks" >> $GITHUB_STEP_SUMMARY
-5
View File
@@ -49,8 +49,3 @@ test-output
# Claude Code local settings
.claude/settings.local.json
# Cursor + Metals
.cursor/
.bloop/
.metals/
-31
View File
@@ -1,31 +0,0 @@
# Vulnerability Research Agent
You are helping a security researcher evaluate and report potential vulnerabilities in Apache Struts.
[`SECURITY.md`](SECURITY.md) is the source of truth for the Apache Struts vulnerability reporting process. **Read it first and follow it.** This file is a short
LLM-facing wrapper around that policy; it does not replace it.
## Workflow
Before drafting any report, opening an issue, posting publicly, or reaching a security conclusion:
1. **Pre-reporting checks** — complete every step in [`SECURITY.md` § Before Reporting](SECURITY.md#before-reporting):
- read the Struts security policy,
- read the Struts security guidelines,
- check previously disclosed vulnerabilities and Security Bulletins.
2. **Assess** the finding against the questions in [`SECURITY.md` § Assessment](SECURITY.md#assessment), and route it through the disposition guide in
[`THREAT_MODEL.md`](THREAT_MODEL.md) (scope, trust boundaries, in-model security properties, and the known-non-findings list). If the answers do not still
point to a likely new framework vulnerability, stop and explain — do not draft a new report.
3. **Report privately** to `security@struts.apache.org` following [`SECURITY.md` § Private Report Requirements](SECURITY.md#private-report-requirements) and
[§ Report Quality Rules](SECURITY.md#report-quality-rules).
Do not open a public GitHub issue, Jira issue, pull request, mailing list thread, or discussion for a suspected vulnerability before private triage.
## Rules for AI Agents
- **Never submit a pull request that fixes a suspected vulnerability.** Before opening any PR, verify the change is not a security patch — OGNL injection,
parameter filtering bypass, file upload exploit, authentication or authorization bypass, RCE, SSRF, path traversal, deserialization, XSS in framework
components, etc. If it is, stop and direct the researcher to report it privately to `security@struts.apache.org` instead. Vulnerability fixes go through
the private security process, not public PRs.
- Do not speculate beyond what can be demonstrated. If severity is uncertain, say so explicitly.
- If the issue turns out to be application misconfiguration, an already-disclosed CVE, or a non-Struts problem, stop and explain — do not draft a new report.
+123 -47
View File
@@ -2,83 +2,159 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
For detailed procedures, use the specialized agents and commands in `.claude/agents/` and `.claude/commands/`.
This document outlines essential practices for working with Claude Code on the Apache Struts project. For detailed procedures, use the specialized agents and commands available in `.claude/agents/` and `.claude/commands/`.
## Project Overview
Apache Struts is a mature MVC web application framework for Java (originally WebWork 2). Current version: **7.2.0-SNAPSHOT**. Uses OGNL for value stack expressions and FreeMarker for UI tag templates.
Apache Struts is a mature MVC web application framework for Java, originally based on WebWork 2. The project follows a modular architecture with clear separation between core framework, plugins, and applications.
### Build Commands
### Build System & Environment
- **Build Tool**: Maven with multi-module structure
- **Java Version**: Java 17+
- **Testing**: JUnit 5 with AssertJ assertions
- **IDE Support**: IntelliJ IDEA with project-specific configurations
### Key Build Commands
```bash
# Run tests (skip assembly for speed)
# Full build with tests
mvn clean install
# Run tests (use /run_tests command for intelligent test execution)
mvn test -DskipAssembly
# Single test in specific module
mvn test -DskipAssembly -pl core -Dtest=MyClassTest#testMethodName
# Jakarta EE 11 / Spring 7 profile
mvn clean install -Pjakartaee11
# Build without running tests
mvn clean install -DskipTests
```
### Project Structure
```
struts/
├── core/ # struts2-core - main framework
├── plugins/ # Plugin modules (json, rest, spring, tiles, velocity, etc.)
├── apps/ # Sample applications (showcase, rest-showcase)
├── assembly/ # Distribution packaging
├── bom/ # Bill of Materials for dependency management
├── parent/ # Parent POM with shared configuration
└── jakarta/ # Jakarta EE compatibility modules
├── core/ # Core framework (struts2-core)
├── plugins/ # Plugin modules (tiles, json, etc.)
├── apps/ # Sample applications (showcase, rest-showcase)
├── assembly/ # Distribution packaging
├── bom/ # Bill of Materials for dependency management
├── parent/ # Parent POM with shared configuration
└── jakarta/ # Jakarta EE compatibility modules
```
### Core Architecture
### Core Architecture Components
**Request Lifecycle**: `Dispatcher``ActionProxy``ActionInvocation` → Interceptor stack → `Action` → Result
#### MVC Framework Components
- **ActionSupport**: Base class for actions with validation and internationalization
- **ActionContext**: Thread-local context holding request/response data
- **ActionProxy/ActionInvocation**: Handles action execution lifecycle
- **Dispatcher**: Core request dispatcher and framework initialization
- **Interceptors**: Cross-cutting concerns (validation, file upload, security)
Key packages in `org.apache.struts2`:
#### Key Packages
- `org.apache.struts2.dispatcher`: Request handling and context management
- `org.apache.struts2.interceptor`: Interceptor implementations
- `org.apache.struts2.components`: UI component system
- `org.apache.struts2.views`: View technologies (JSP, FreeMarker, Velocity)
- `org.apache.struts2.security`: Security-related utilities
- `dispatcher` - Request handling, `Dispatcher`, servlet integration
- `interceptor` - Built-in interceptors (params, validation, fileUpload)
- `components` - UI tag components (form, textfield, submit)
- `action` - Action interfaces (`UploadedFilesAware`, `SessionAware`, etc.)
- `security` - Security utilities and OGNL member access policies
### Technology Stack
- **Jakarta EE**: Servlet API, JSP, JSTL
- **Core Libraries**: OGNL (expression language), Commons FileUpload2, Log4j2
- **Template Engines**: FreeMarker, Velocity (via plugins)
- **Build Dependencies**: Maven, various plugins for assembly and site generation
## Security-Critical Patterns
## Security-First Development
Apache Struts has a history of security vulnerabilities (OGNL injection, temp file exploits). Apply these Struts-specific patterns:
### Critical Security Principles
1. **Never create files in system temp directories** - always use controlled application directories
2. **Use UUID-based naming** for temporary files to prevent collisions and path traversal
3. **Implement proper resource cleanup** with try-with-resources and finally blocks
4. **Track all temporary resources** for explicit cleanup (security critical)
5. **Validate all user inputs** and sanitize filenames before processing
1. **Temporary files**: Use UUID-based names in controlled locations (see example below)
2. **OGNL expressions**: Evaluate only framework-generated OGNL; use allowlist member access
3. **File uploads**: Validate content types, sanitize filenames, enforce size limits
4. **Parameter filtering**: Use `ParameterNameAware` to restrict accepted parameter names
**For comprehensive security analysis, use:** `/security_scan`
### Security Implementation Patterns
```java
// Secure temporary file pattern
// GOOD: Secure temporary file creation
protected File createTemporaryFile(String fileName, Path location) {
String uid = UUID.randomUUID().toString().replace("-", "_");
return location.resolve("upload_" + uid + ".tmp").toFile();
File file = location.resolve("upload_" + uid + ".tmp").toFile();
LOG.debug("Creating temporary file: {} (originally: {})", file.getName(), fileName);
return file;
}
```
## Security Reports & Scans
## Testing Implementation
For any security-related activity — vulnerability scans, security analysis, drafting security reports — **[`SECURITY.md`](SECURITY.md) is the source of truth**.
Read it first and follow its pre-reporting checks, assessment checklist, and reporting requirements. Reports must be sent privately to
`security@struts.apache.org`; do not open a public GitHub issue, Jira issue, pull request, or mailing list thread for a suspected vulnerability before private
triage. [`AGENTS.md`](AGENTS.md) is a shorter LLM-facing wrapper around the same process.
**For intelligent test execution and analysis, use:** `/run_tests`
## Testing
### Test Structure & Coverage
- **Unit Tests**: Test individual methods with mocked dependencies
- **Integration Tests**: Test complete workflows with real file I/O
- **Security Tests**: Verify directory traversal prevention, secure naming
- **Error Handling Tests**: Test exception scenarios and error reporting
- **Cleanup Tests**: Verify resource cleanup and tracking
Tests use JUnit 5 with AssertJ assertions and Mockito for mocking. Run with `mvn test -DskipAssembly`.
### Basic Testing Command
```bash
# Run all tests (use -DskipAssembly to avoid building docs/examples)
mvn test -DskipAssembly
```
## Pull Requests
## Documentation Standards
- **Title format**: `WW-XXXX Description` (Jira ticket ID required)
- **Link ticket in description**: `Fixes [WW-XXXX](https://issues.apache.org/jira/browse/WW-XXXX)`
- **Issue tracker**: https://issues.apache.org/jira/projects/WW
- **Never submit a PR that fixes a suspected vulnerability.** Before opening a PR, verify the change is not a security patch (OGNL injection, parameter
filtering bypass, file upload exploit, auth bypass, RCE, SSRF, path traversal, deserialization, XSS in framework components, etc.). If it is, stop and report
it privately to `security@struts.apache.org` — see [`SECURITY.md`](SECURITY.md).
**For comprehensive documentation quality analysis, use:** `/quality_check`
### Documentation Requirements
- **Always document security implications** in methods handling files/user input
- **Include usage examples** for complex methods and classes
- **Document exception conditions** and error handling behavior
- **Reference related methods** using `@see` tags
- **Explain resource management** responsibilities
## Error Handling & Logging
### Logging Best Practices
- Use parameterized logging for performance: `LOG.debug("Processing: {}", value)`
- Log security-relevant operations appropriately
- Use appropriate log levels (debug/info/warn/error)
- Avoid logging sensitive information
## Code Quality Standards
**For comprehensive code quality analysis, use:** `/quality_check`
### Key Principles
- Use `protected` for methods that subclasses might override
- Catch specific exceptions rather than generic `Exception`
- Use clear, descriptive method and variable names
- Follow existing project conventions and patterns
## Available Automated Tools
### Commands
- `/security_scan` - Comprehensive security analysis
- `/run_tests` - Intelligent test execution and analysis
- `/quality_check` - Code quality and documentation analysis
- `/config_analyze` - Configuration validation and optimization
- `/create_plan` - Implementation planning assistance
- `/validate_plan` - Plan validation and verification
- `/commit` - Guided git commit creation
- `/research_codebase` - Comprehensive codebase research
### Specialized Agents
- `security-analyzer` - OGNL injection scanning, CVE detection
- `test-runner` - Maven test execution and coverage analysis
- `code-quality-checker` - JavaDoc compliance, pattern consistency
- `config-validator` - struts.xml validation, interceptor analysis
- `jakarta-migration-helper` - Jakarta EE migration assistance
- `codebase-analyzer` - Project structure and architecture analysis
- `codebase-locator` - Code and file location assistance
- `codebase-pattern-finder` - Pattern examples and usage
## Common Pitfalls to Avoid
1. **File Security**: Never use `File.createTempFile()` without directory control
2. **Resource Leaks**: Always track and clean up temporary files
3. **Test Coverage**: Don't forget to test error conditions and cleanup
4. **Documentation**: Always document security implications
5. **Exception Handling**: Don't let cleanup failures affect main operations
6. **Path Validation**: Always validate and sanitize file paths
-118
View File
@@ -1,118 +0,0 @@
<!---
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
# Contributing to Apache Struts
Thanks for your interest in contributing! Apache Struts is maintained by a
community of volunteers under the [Apache Software Foundation](https://www.apache.org/).
This guide walks a first-time contributor from a fresh clone to a merged pull
request. You do not need to be a committer to contribute — anyone can open a PR.
## Getting help
- **Mailing lists:** Subscribe and ask on the developer or user list — see
<https://struts.apache.org/mail.html>. The developer list is the best place
to discuss a change before you start larger work.
- **Issue tracker:** [JIRA WW project](https://issues.apache.org/jira/projects/WW).
- **Homepage & docs:** <https://struts.apache.org/>.
If you are unsure whether a change is wanted, ask on the developer list or
comment on the relevant JIRA issue first.
## Project overview
Apache Struts is a mature MVC web framework for Java (originally WebWork 2). It
uses OGNL for value-stack expressions and FreeMarker for UI tag templates. The
repository is a multi-module Maven build:
| Module | Responsibility |
|------------|-------------------------------------------------------------|
| `core` | `struts2-core` — the main framework |
| `plugins` | Plugin modules (json, rest, spring, tiles, velocity, …) |
| `apps` | Sample applications (showcase, rest-showcase) |
| `assembly` | Distribution packaging |
| `bom` | Bill of Materials for dependency management |
| `parent` | Parent POM with shared configuration |
| `jakarta` | Jakarta EE compatibility modules |
The request lifecycle is `Dispatcher``ActionProxy``ActionInvocation`
interceptor stack → `Action``Result`.
## Prerequisites & building
- **JDK 17** and **Maven**.
- Run the tests (skipping assembly for speed):
```bash
mvn test -DskipAssembly
```
- Run a single test in a specific module:
```bash
mvn test -DskipAssembly -pl core -Dtest=MyClassTest#testMethodName
```
- Build against the Jakarta EE 11 / Spring 7 profile:
```bash
mvn clean install -Pjakartaee11
```
Tests use JUnit 5 with AssertJ assertions and Mockito for mocking.
## Finding something to work on
Browse the [JIRA WW project](https://issues.apache.org/jira/projects/WW) for
open issues. Comment on an issue to let others know you are working on it. If
no ticket exists for your change, **file one first** — every commit and pull
request must reference a `WW-XXXX` ticket ID.
## Development workflow
1. Fork the repository and clone your fork.
2. Create a branch off `main` named after the ticket, e.g. `WW-1234-short-description`.
3. Implement your change **with tests**. Keep commits focused.
4. Prefix every commit message with the ticket ID: `WW-1234 Describe the change`.
5. Run `mvn test -DskipAssembly` and make sure it passes before opening a PR.
## Submitting a pull request
- **Title format:** `WW-XXXX Description` (the JIRA ticket ID is required).
- **Link the ticket** in the description:
`Fixes [WW-XXXX](https://issues.apache.org/jira/browse/WW-XXXX)`.
- Continuous integration must pass, and reviewers expect code changes to come
with tests.
## Reporting security issues
**Do not** open a public GitHub issue, JIRA issue, pull request, or
mailing-list thread for a suspected vulnerability. Report it privately to
**security@struts.apache.org**. See [`SECURITY.md`](SECURITY.md) for the full
process. This includes OGNL injection, parameter-filtering bypasses, file
upload exploits, authentication bypass, RCE, SSRF, path traversal,
deserialization, and XSS in framework components.
## Licensing & Code of Conduct
- Apache Struts is licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
- Every new source file must include the standard ASF license header (see any
existing source file or this file's header for the exact text).
- By submitting a pull request you agree to license your contribution under the
Apache License 2.0. The ASF does not require a separate signed CLA for typical
contributions.
- All participation is governed by the
[ASF Code of Conduct](https://www.apache.org/foundation/policies/conduct.html).
Vendored
+3 -21
View File
@@ -1,22 +1,4 @@
#!groovy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
pipeline {
agent none
@@ -105,7 +87,7 @@ pipeline {
when {
anyOf {
branch 'main'
branch 'support/struts-6-x-x'
branch 'release/struts-6-7-x'
}
}
steps {
@@ -119,7 +101,7 @@ pipeline {
when {
anyOf {
branch 'main'
branch 'support/struts-6-x-x'
branch 'release/struts-6-7-x'
}
}
steps {
@@ -132,7 +114,7 @@ pipeline {
when {
anyOf {
branch 'main'
branch 'support/struts-6-x-x'
branch 'release/struts-6-7-x'
}
}
steps {
+7 -125
View File
@@ -1,30 +1,17 @@
# Security Policy
## Threat Model
A structured threat model for the Apache Struts framework — scope, adversary model,
the security properties the framework provides vs. leaves to the application, and a
triage-disposition guide for inbound reports and automated-scanner findings — is
maintained in [`THREAT_MODEL.md`](THREAT_MODEL.md). It is additive to this policy:
this `SECURITY.md` and the [security guidance](https://struts.apache.org/security/)
remain canonical for the reporting process and configuration details.
## Supported Versions
Please visit the [Releases](https://struts.apache.org/releases.html#prior-releases) page to see full information about each version
and what potential vulnerability it can have:
| Version | Supported |
|---------|-----------|
| 7.x | yes |
| 6.x.x | yes |
| 2.5.x | no |
| 2.3.x | no |
| 2.2.x | no |
| 2.1.x | no |
| 2.0.x | no |
| Version | Supported |
|---------|--------------------|
| 7.x | :white_check_mark: |
| 6.7.x | :white_check_mark: |
| 2.5.x | |
## Reporting New Security Issues with the Apache Struts
## Reporting New Security Issues with thr Apache Struts
([original](https://struts.apache.org/security.html))
@@ -42,7 +29,7 @@ All mail sent to this address that does not relate to security problems in the A
```
Note that all networked servers are subject to denial of service attacks, and we cannot promise magic
workarounds to generic problems (such as a client streaming lots of data to your server or requesting
workarounds to generic problems (such as a client streaming lots of data to your server, or requesting
the same URL repeatedly). In general, our philosophy is to avoid any attacks that can cause the server
to consume resources in a non-linear relationship to the size of inputs.
@@ -51,108 +38,3 @@ The mailing address is: [security@struts.apache.org](mailto:security@struts.apac
[General network server security tips](http://httpd.apache.org/docs/trunk/misc/security_tips.html)
[The Apache Security Team](http://www.apache.org/security/)
## Do not disclose through a pull request, commit, or issue
**A fix is a disclosure.** Opening a public pull request, pushing a commit, branch, or
fork, or filing a public Jira/GitHub issue that **fixes, describes, or hints at** a
suspected vulnerability reveals where the weakness is — often with a working roadmap to
exploit it — before a fixed release exists. This holds even if you never attach a
proof-of-concept, and even if you believe the impact is low or you are "just hardening"
the code.
If you have found, or suspect you have found, a security problem:
- **Do not** open a public PR, commit, branch, fork, Jira issue, or mailing-list thread
for it.
- **Do** email [security@struts.apache.org](mailto:security@struts.apache.org) first and
wait for the PMC to triage it and agree how the fix will be handled — the fix is
typically prepared privately and landed alongside the advisory and release.
If you notice a possible security issue while working on an unrelated bug or PR, stop and
email the private list before pushing the change. **When in doubt, treat it as
security-sensitive and email the list** — a private report that turns out to be a
non-issue costs far less than a public change that turns out to be exploitable.
## Before Reporting
Before sending a vulnerability report, run through the following checks. They exist to prevent duplicate reports, public disclosure of untriaged issues,
and reports for behavior that is already documented as insecure configuration.
### 1. Read this policy
Confirm:
- which Struts versions are currently supported (see [Supported Versions](#supported-versions)),
- where reports must be sent (see [Reporting New Security Issues](#reporting-new-security-issues-with-the-apache-struts)),
- which reports do not belong on the private security list.
### 2. Read the Struts security guidelines
Review the [Struts security guidance](https://struts.apache.org/security/) and determine whether the finding is already covered by documented secure
configuration or application guidance, including but not limited to:
- Config Browser Plugin exposure,
- direct JSP access,
- `devMode` is required to exploit the vulnerability,
- `@StrutsParameter` usage and parameter annotation requirements,
- unsafe setters or getters exposed to request parameters,
- use of incoming values in localization or forced OGNL evaluation,
- raw JSP EL expressions,
- custom error pages,
- Dynamic Method Invocation and Strict Method Invocation,
- accepted and excluded parameter patterns,
- Fetch Metadata, COOP, and COEP protections,
- OGNL sandboxing, allowlists, excluded classes/packages, and OGNL Guard settings.
If the behavior is caused by an application ignoring documented security guidance, that is not an Apache Struts framework vulnerability.
### 3. Check previously disclosed vulnerabilities
Compare the finding against already disclosed Struts vulnerabilities — affected versions, impact ratings, mitigations, and fixed versions:
- [Struts security information](https://struts.apache.org/security/)
- [Prior releases and vulnerability notes](https://struts.apache.org/releases.html#prior-releases)
- [Security Bulletins (S2 series)](https://cwiki.apache.org/confluence/display/WW/Security+Bulletins)
If the finding overlaps with a known vulnerability, link to the existing bulletin, advisory, CVE, or release notes instead of drafting a new report.
## Assessment
Before drafting a report, confirm:
1. Is the affected version supported?
2. Is the behavior in Apache Struts framework code, rather than only in an application using Struts?
3. Is it already documented as insecure configuration or unsupported usage?
4. Is it a duplicate of a previously disclosed vulnerability or Security Bulletin?
5. Can the impact be demonstrated with a minimal, self-contained reproduction?
Only proceed with a private report when these answers still point to a likely new vulnerability in the framework.
## Private Report Requirements
A useful private report includes:
- affected Struts version or version range,
- affected component or module,
- required application configuration, if any,
- minimal reproduction steps,
- expected behavior,
- actual behavior,
- demonstrated security impact,
- whether authentication or special privileges are required,
- proposed fix or mitigation, if known.
Do not speculate beyond what can be demonstrated. If severity is uncertain, say so explicitly.
## Report Quality Rules
- One vulnerability per report.
- Keep reproduction steps minimal and self-contained.
- Do not include unrelated findings.
- Do not publish exploit details or proof-of-concept code publicly before the Struts project has triaged the issue. **A fix, patch, or hardening change is a
public disclosure in the same way a PoC is** — see [Do not disclose through a pull request, commit, or issue](#do-not-disclose-through-a-pull-request-commit-or-issue).
**Pushing a PoC to a public GitHub repository, gist, fork, or branch counts as public disclosure** — even a "test" or throwaway repo. Private repositories
are acceptable for sharing a PoC, but access must be granted individually to each PMC member who will triage the report.
- Do not send ordinary bugs, usage questions, or generic denial-of-service concerns to the private security list.
- If the issue is not a vulnerability in Apache Struts source code, use the appropriate public support or issue channel instead.
-439
View File
@@ -1,439 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
# Apache Struts — Threat Model (v0 draft)
## §1 Header
- **Project:** Apache Struts (`apache/struts`), `main` @ HEAD (2026-06). Scope: the
Struts framework in `apache/struts` only (the core MVC framework, its
interceptors, tags, and the plugins shipped in this repo).
- **Date:** 2026-06-24. **Drafted for PMC review** via the threat-model-producer
rubric (Scovetta). This is an unratified proposal, not an ASF Security team or
PMC position; authorship and sponsorship are settled only once the PMC adopts it
(see Status below and §14).
- **Status:** DRAFT — not yet reviewed by the Struts PMC. Built as a strict
superset of the existing [`SECURITY.md`](SECURITY.md) and the published
[Struts security guidance](https://struts.apache.org/security/); every
load-bearing claim is tagged for provenance (see §14 for open questions).
- **Version binding:** versioned with the project; a report against version *N*
is triaged against the model as it stood at *N*. The security envelope changed
materially at **7.0** (several hardening knobs flipped to secure-by-default —
§5a), so the version is itself load-bearing.
- **Reporting cross-reference:** §8-property violations → report privately per
[`SECURITY.md`](SECURITY.md) (`security@struts.apache.org`); §3/§9/§11a findings
are closed citing this document and the existing `SECURITY.md` "Before
Reporting" checks.
- **Provenance legend:** *(documented)* = Struts' own docs/`SECURITY.md`/security
site; *(maintainer)* = confirmed by a Struts PMC member through this process;
*(inferred)* = reasoned from architecture/docs, not yet PMC-ratified — each has
a matching §14 open question.
- **Draft confidence:** the bulk is *(documented)* — Struts has an unusually rich
published security policy — with a handful of *(inferred)* scoping calls for the
PMC to ratify.
**What Struts is.** Apache Struts 2 is a **Java MVC web framework** for building
server-side web applications. A request flows: servlet filter → action mapping →
**interceptor stack** (parameter population, validation, etc.) → **Action**
**result** (typically a JSP/FreeMarker view). Request parameters are bound onto
action properties via setters, and view/configuration expressions are evaluated
through **OGNL (Object-Graph Navigation Language)** against the **ValueStack**.
*(documented — struts.apache.org)*
**The framework's own security philosophy (load-bearing).** Struts
**"doesn't provide any security mechanism — it is just a pure web framework."**
*(documented — [security guidance](https://struts.apache.org/security/))* It is
not an authentication, authorization, session-security, or input-sanitisation
layer; those are the embedding application's responsibility (§3/§10). What Struts
*does* take an active stance on is **not letting its own machinery — chiefly OGNL
expression evaluation and request-parameter binding — become an injection vector**.
That single sentence shapes the whole model: most "Struts is insecure" reports are
either OGNL-injection-class (in model, §8) or application-responsibility (out of
model, §3/§11a).
## §2 Scope and intended use
Intended deployment: the Struts JARs are a **dependency embedded inside a web
application** (a WAR) that the application developer writes, configures, and
deploys into a servlet container (Tomcat, Jetty, …) behind the operator's
perimeter. Struts is **in-process** with the application; it has no daemon, no
listening socket of its own, and no trust boundary against the application code
it runs inside. *(documented — it is a framework, not a server.)*
**Caller roles.**
- **Untrusted HTTP client** — sends requests (parameters, headers, cookies,
multipart uploads) to a Struts-backed endpoint. **The primary untrusted boundary.**
Struts must treat all request-derived values as hostile. *(documented — the
parameter/OGNL hardening exists precisely for this actor.)*
- **Application developer** — writes the actions, JSPs, struts.xml/annotations,
and chooses the hardening settings (§5a). **Trusted by the framework** — their
code and configuration run with the application's privileges. A finding that
requires the developer to write unsafe code or disable a default protection is
the application's bug, not Struts' (§3). *(documented — the developer-responsibility
section of the security guidance.)*
- **Operator** — deploys the WAR, sets `devMode` off, restricts dev-only plugins,
configures the container and JVM. **Trusted.** *(documented.)*
**Component families.**
| Family | Entry point | Touches | In model? |
| --- | --- | --- | --- |
| OGNL evaluation + ValueStack | expression eval for params, tags, results | in-JVM code paths | **In — the central attack surface** *(documented)* |
| Parameter binding (`ParametersInterceptor`, `@StrutsParameter`) | request params → action setters | reflection into app objects | **In — primary boundary** *(documented)* |
| Interceptor stack (cookie, fileupload, fetch-metadata, COOP/COEP, …) | per-request processing | request data | **In** *(documented)* |
| Tag library / JSP & FreeMarker integration | view rendering, expression output | template eval | **In — output-side OGNL/EL** *(documented)* |
| File upload (Jakarta multipart) | multipart request parsing | temp files | **In — historical CVE surface** *(documented — S2 bulletins)* |
| Bundled plugins (REST, JSON, Convention, …) in this repo | extra mappers/result types | request data | **In — same request-trust surface** *(inferred — §14 Q-plugins)* |
| Config Browser Plugin | exposes internal config | dev-only diagnostic | **In as dev-only** — exposure in prod is operator misconfig (§3/§11a) *(documented)* |
| Embedding application's own actions/JSPs/config | the developer's code | as the app | **Out — application responsibility (§3)** *(documented)* |
| Examples / showcase / test apps | demo code | n/a | **Out** *(see §3)* |
## §3 Out of scope (explicit non-goals)
The detailed lists of developer anti-patterns and insecure configurations are
maintained in the project's own docs and are **not duplicated here** — this model
links to them and assigns each a triage disposition (§13):
- **Anything the application developer is responsible for.** Struts provides no
security mechanism of its own *(documented)*. The full enumeration —
developer-exposed unsafe setters, request parameters used in localization or
forced OGNL evaluation, raw `${...}` JSP-EL over untrusted values, direct JSP
access, mixing security levels in one namespace — is in the
[security guidance](https://struts.apache.org/security/) and
[`SECURITY.md`](SECURITY.md). All are `OUT-OF-MODEL: application-responsibility`.
- **Findings that only manifest with a documented-insecure / non-default setting**
(`devMode=true`, Config Browser Plugin exposed in production, DMI enabled, or a
§5a hardening knob turned off) → `OUT-OF-MODEL: non-default-config`. *(documented.)*
- **The servlet container, JVM, JDK, and OS**, and the application's own
authentication, authorization, session management, CSRF token storage, and
transport (TLS). Struts is "a pure web framework," not a security framework.
*(documented / inferred — §14 Q-env.)*
- **Generic denial of service.** Per [`SECURITY.md`](SECURITY.md), generic flooding
or large-body streaming is not accepted; only *super-linear* amplification inside
framework code may be in model (§8 / §14 Q-dos). *(documented.)*
- **Already-disclosed S2-series vulnerabilities** — a duplicate of an existing
Security Bulletin/CVE is closed by reference (the
[`SECURITY.md` "Before Reporting"](SECURITY.md) checks), not re-triaged.
- **Examples, showcase, and test applications** shipped in the repo. *(inferred — §14 Q-scope.)*
## §4 Trust boundaries and data flow
```
Untrusted HTTP request
│ params, headers, cookies, multipart
Servlet filter ─► action mapping ─► Interceptor stack ─► Action ─► Result (JSP/FreeMarker)
│ │
ParametersInterceptor tag/result OGNL eval
binds params to setters against ValueStack
│ │
▼ ▼
OGNL evaluation against the ValueStack ◄── the trust boundary
(allowlist / excluded classes+packages /
expression length / @StrutsParameter)
```
- **HTTP client → framework** is the one boundary Struts owns. Every request-derived
string (parameter *names* as well as *values*, cookie names/values, header values,
multipart filenames) is untrusted and may carry an OGNL payload. The framework's
job at this boundary is to bind parameters and evaluate expressions **without
letting attacker input reach an OGNL evaluation that creates or changes executable
code**. *(documented.)*
- **Framework → application code** is *not* a trust boundary — Struts runs the
developer's actions and templates in-process, fully trusted. *(documented.)*
**Reachability precondition (triager's test).** A finding is in-model only if it is
reachable by an **untrusted HTTP client against a Struts application that follows the
documented secure configuration** (current-version defaults, `devMode` off, dev-only
plugins restricted, no developer anti-patterns from §3). A finding that needs
`devMode`, a disabled default protection, a developer-introduced unsafe setter, or a
documented anti-pattern is `OUT-OF-MODEL`. *(documented/inferred — §14 Q-default.)*
## §5 Assumptions about the environment
- A servlet container and a JVM the operator maintains; Struts does not patch or
harden them. *(inferred — §14 Q-env.)*
- The application is deployed with the **current supported version** (7.x or 6.x per
`SECURITY.md`); 2.x is end-of-life and out of support. *(documented — Supported
Versions table.)*
- The operator runs production with `devMode=false` and dev-only diagnostics (Config
Browser Plugin) disabled or access-controlled. *(documented.)*
- Struts opens no sockets and makes no outbound connections of its own; any network
egress is the application's. *(inferred — §14 Q-egress.)*
## §5a Build-time and configuration variants — **the central knob set**
Struts' security envelope is set almost entirely by **runtime configuration**. The
**authoritative, current list of every hardening setting (purpose + secure default)
lives in the [security guidance](https://struts.apache.org/security/) and is not
reproduced here.** Only the triage-load-bearing facts:
- The security posture **changed materially at 7.0**, where a cluster of
OGNL-injection and parameter-binding defences became **secure-by-default**
notably the OGNL allowlist (`struts.allowlist.enable`), the `@StrutsParameter`
annotation requirement (`struts.parameters.requireAnnotations`), excluded
classes/packages, the expression-length cap (`struts.ognl.expressionMaxLength`,
default 256), and the static-field/proxy/default-package/custom-map disallows.
- `struts.devMode` (must be `false` in production) and Dynamic Method Invocation
(gated by Strict Method Invocation since 2.5) are the two settings whose *insecure*
value most often turns a non-finding into an apparent finding.
- The **FetchMetadata / COOP / COEP** interceptors (6.0+) are opt-in cross-origin
defences (§8.5).
**Insecure-default question (wave 1).** Because the secure posture is the **7.0
default set**, the triage rule needs ratifying: is "a finding that only works with a
pre-7.0 default, or with a 7.0 hardening knob turned off" `OUT-OF-MODEL:
non-default-config`, with §10 carrying "deploy current version with defaults"? — §14
Q-default. The OGNL **Java Security Manager sandbox** (`-Dognl.security.manager`) is a
separate, opt-in defence built on the JDK `SecurityManager`, which has been
**deprecated for removal since JDK 17 (JEP 411), disabled by default since JDK 18,
and permanently disabled in JDK 24 (JEP 486)** *(documented — JDK release notes)*
so on modern JDKs the model cannot treat it as a relied-upon control (§14 Q-jsm).
## §6 Assumptions about inputs
| Surface | Input | Attacker-controllable? | Concern |
| --- | --- | --- | --- |
| Parameter binding | request parameter **names and values** | **yes** | OGNL injection via crafted names; binding to unsafe setters |
| Cookies | cookie names/values (Cookie Interceptor) | **yes** | same OGNL/parameter concerns; checked by accepted/excluded patterns |
| Headers | request headers | **yes** | header-driven expression/log paths |
| Multipart upload | file content, filename, content-type | **yes** | parser robustness, temp-file handling (S2 history) |
| Expression context | values that reach an OGNL eval (tags, results, forced eval) | **yes if developer feeds untrusted input in** | the core RCE channel |
| struts.xml / annotations / action code | framework + app configuration | **no — developer-trusted** | not an attacker surface (§3) |
The accepted/excluded pattern checkers (`AcceptedPatternsChecker` /
`ExcludedPatternsChecker`, since 2.3.20) validate parameter names/values for the
Parameters and Cookie interceptors; a custom override that drops below the framework
defaults is a developer error, not a framework flaw. *(documented.)*
## §7 Adversary model
- **In scope:** an **untrusted remote HTTP client** with no credentials, able to send
arbitrary parameters, headers, cookies, and multipart uploads to any
Struts-handled endpoint. Capabilities: craft parameter names/values carrying OGNL,
attempt to reach executable-code creation through the ValueStack, pollute
parameter binding, exploit a file-upload or multipart parsing bug, or trigger a
super-linear resource path in framework code. Goal: **remote code execution via
OGNL** (the dominant Struts threat), and secondarily data disclosure, SSRF through
framework features, or DoS amplification. *(documented — the OGNL lineage is the
framework's stated central concern.)*
- **On-path network attacker** — only where the application/operator has not deployed
TLS; transport security is the app's, so this is largely out of model (§3). *(inferred — §14 Q-env.)*
- **Out of scope:** the application developer (writes trusted code/config); the
operator (deploys, sets devMode/plugins); anyone with container/host/JVM control;
and a developer who disables a default protection or follows a documented
anti-pattern (§3). *(documented.)*
## §8 Security properties the framework provides
*(In the current-version, default-hardening posture; each lists violation symptom +
severity. Most are documented controls — the OGNL-injection defences are the core of
Struts' security work.)*
1. **OGNL injection containment.** Attacker-supplied request data (parameter names/
values, cookies, headers) must not reach an OGNL evaluation that creates or alters
executable code. Enforced in depth by the default controls listed in §5a / the
[security guidance](https://struts.apache.org/security/) (allowlist, excluded
classes/packages, expression-length cap, static-field/proxy/default-package/
custom-map disallows, excluded node types). *Violation:* a crafted request
achieving OGNL-driven code execution (or class-loader/member access beyond the
allowlist) on a default-configured current-version app. *Severity:*
security-critical (the S2-RCE class). *(documented.)*
2. **Parameter-binding safety (7.0).** Request parameters bind only to setters the
developer marked `@StrutsParameter` (to the declared depth); arbitrary deep/nested
property traversal is not reachable by default. *Violation:* parameters reaching
an unannotated setter, or nesting beyond the declared depth, on a default 7.0 app.
*Severity:* critical. *(documented.)*
3. **Method-invocation control.** Dynamic Method Invocation is gated by Strict Method
Invocation; a client cannot invoke arbitrary action methods by name when DMI is at
its recommended (off/strict) setting. *Violation:* arbitrary method invocation on a
default app. *Severity:* highcritical. *(documented.)*
4. **Expression-length and node-type bounds.** OGNL expressions over the configured
length (default 256) and forbidden node types are rejected before evaluation.
*Violation:* bypass of these bounds. *Severity:* high. *(documented.)*
5. **Cross-origin / fetch-metadata defences (opt-in).** When the FetchMetadata, COOP,
and COEP interceptors are enabled, the framework emits/enforces the corresponding
`Sec-Fetch-*` and cross-origin isolation behaviour. *Violation:* the interceptor
failing to enforce its documented behaviour when enabled. *Severity:* mediumhigh.
*(documented — opt-in since 6.0.)*
## §9 Security properties the framework does *not* provide
- **No security mechanism in the general sense.** Struts provides no authentication,
authorization, session security, CSRF token store, input sanitisation, or output
encoding *for the application's own data* — "it is just a pure web framework."
*(documented.)*
- *False friend:* "Struts has no built-in login/access control" is **by design**,
not a vulnerability.
- **No protection against developer anti-patterns or non-default config** — unsafe
setters, raw `${}` on user input, request params in localization/forced eval,
direct JSP access, `devMode` on, disabled hardening (§3/§5a).
- **No defence once OGNL evaluation is fed untrusted input by the application
itself** (forced expression evaluation on a request value) — that is the developer
handing OGNL the attacker's string. *(documented.)*
- **No hard anti-DoS guarantee** beyond the "avoid super-linear in input size"
philosophy; generic flooding/streaming DoS is the operator's to absorb. *(documented.)*
- **The OGNL Java Security Manager sandbox is not a relied-upon control on modern
JDKs** (the underlying `SecurityManager` is deprecated for removal since JDK 17 and
permanently disabled in JDK 24; see §5a). *(documented.)*
- **Auto-generated error pages do not escape action names** (historical S2-006) — the
app must define custom error pages; XSS in the default error page is a documented
hardening item, not a defended property. *(documented.)*
- **Well-known classes (framework):** OGNL/expression injection, multipart/file-upload
parsing bugs, and parameter-pollution are the framework's recurring risk classes;
reflected XSS, CSRF token management, and transport security are the application's.
## §10 Downstream (developer + operator) responsibilities
The full, authoritative how-to is the [security guidance](https://struts.apache.org/security/)
and [`SECURITY.md`](SECURITY.md); in one line: **deploy a current supported version
with the default hardening left on, `devMode` off, dev-only plugins restricted,
parameter setters annotated, JSPs hidden behind actions, and the application's own
authn/authz/CSRF/TLS supplied** (Struts provides none of those). The threat-model
value is only that a finding requiring the developer to *violate* one of these is
`OUT-OF-MODEL` (§3/§13), not that this list is novel.
## §11 Known misuse patterns
These are the §3 application-responsibility / non-default-config items viewed as
"things integrators get wrong" — running `devMode=true` in production or exposing the
Config Browser Plugin; disabling a default OGNL/binding protection "to make something
work"; exposing unsafe setters to binding; feeding request parameters into forced
OGNL evaluation or localization; allowing direct `*.jsp` access or raw `${}` EL on
untrusted values; relying on the OGNL Java Security Manager sandbox on modern JDKs. Each
is documented in the [security guidance](https://struts.apache.org/security/); the
disposition mapping is §11a/§13.
## §11a Known non-findings (recurring false positives)
*(Seeded directly from `SECURITY.md` "Before Reporting" — the PMC owns the
authoritative list; §14 Q12.)*
- **"OGNL/RCE that only works with `devMode=true`."** `OUT-OF-MODEL: non-default-config`
— devMode is a development-only setting documented as unsafe for production.
- **"An action setter lets me inject a value / reach a dangerous method."** When the
setter is developer-exposed without `@StrutsParameter` (7.0), or performs an unsafe
side effect, this is `OUT-OF-MODEL: application-responsibility`. In-model only if it
bypasses the framework's *default* binding/OGNL protections.
- **"Direct JSP access discloses X / executes Y."** App-deployment misconfiguration —
JSPs must be hidden behind actions. `OUT-OF-MODEL: application-responsibility`.
- **"Raw `${}` EL / forced OGNL eval on my request parameter is exploitable."** The
application fed untrusted input to expression evaluation — documented anti-pattern,
not a framework flaw.
- **"Config Browser Plugin exposes internal configuration."** Dev-only diagnostic;
exposing it in production is operator misconfiguration. `OUT-OF-MODEL: non-default-config`.
- **"I can enumerate / pass arbitrary parameters."** Parameter binding is the point of
the framework; in-model only when it crosses the default annotation/allowlist
protections.
- **"Generic DoS: I streamed a huge body / hammered a URL."** Not accepted per
`SECURITY.md`; only super-linear amplification inside framework code is considered.
- **Duplicate of a disclosed S2-series bulletin/CVE** — closed by reference.
- **Dependency-tail CVEs** (a transitive jar, e.g. a logging or XML library) from an
SCA scan — triage upstream unless Struts' own code reaches the vulnerable path with
untrusted input.
## §12 Conditions that would change this model
- A change to the default-hardening set (e.g. a new secure-by-default knob, or a
default flipped) — re-baseline §5a/§8/§11a.
- A new request-facing surface, a new bundled plugin, or a new expression/templating
integration with its own trust surface.
- A change to how OGNL evaluation, the allowlist, or parameter binding works.
- A report that cannot be routed to a §13 disposition → revise §8/§9.
## §13 Triage dispositions
| Disposition | Meaning | Licensed by |
| --- | --- | --- |
| `VALID` | A §8 property breaks via an untrusted HTTP client on a current-version, default-hardened app. | §8, §6, §7 |
| `VALID-HARDENING` | A §11 misuse is too easy, or a default could be tightened. | §11/§5a |
| `OUT-OF-MODEL: application-responsibility` | Requires a developer anti-pattern (unsafe setter, raw EL, forced eval, direct JSP) or the app's own authn/authz. | §3/§10 |
| `OUT-OF-MODEL: non-default-config` | Only manifests with `devMode`, a dev-only plugin, DMI, or a disabled default protection. | §5a |
| `OUT-OF-MODEL: adversary-not-in-scope` | Requires container/host/JVM/developer control. | §7 |
| `OUT-OF-MODEL: unsupported-version` | Only affects an end-of-life (2.x) version. | §5 |
| `BY-DESIGN: property-disclaimed` | Concerns a property §9 disclaims (no built-in authn/authz/encoding; generic DoS; JSM on JDK21+). | §9 |
| `KNOWN-NON-FINDING` | Matches §11a. | §11a |
| `DUPLICATE` | Matches a disclosed S2-series bulletin/CVE. | §3 |
| `MODEL-GAP` | Unroutable. | triggers §12 |
## §14 Open questions for the maintainers
**Wave 1 — scope, defaults, intended use**
- **Q-default.** Confirm the triage baseline is "current supported version (7.x/6.x)
with the documented default hardening on, `devMode` off, dev-only plugins
restricted" — and that a finding requiring a pre-7.0 default or a disabled hardening
knob is `OUT-OF-MODEL: non-default-config`. (§5a/§13.)
- **Q-scope.** Confirm the in-scope surface is the framework in `apache/struts`
(core + interceptors + tags + bundled plugins), with the embedding application's own
actions/JSPs/config, and examples/showcase, out of scope. (§2/§3.)
- **Q-philosophy.** Confirm the framing that Struts provides **no security mechanism
of its own** beyond OGNL/parameter-binding injection containment — i.e. authn,
authz, session security, CSRF token storage, output encoding, and transport are the
application's. (§9.)
- **Q-env.** Confirm the servlet container, JVM, JDK, and OS are out of scope — Struts
does not patch or harden them, and the operator maintains them. (§3/§5.)
- **Q-egress.** Confirm Struts opens no sockets and makes no outbound connections of
its own, so any network egress (and the SSRF surface it implies) is the
application's. (§5/§7.)
**Wave 2 — mechanism confirmations**
- **Q-ognl.** Confirm the §8.1 list is the authoritative set of default OGNL-injection
defences (allowlist, excluded classes/packages/patterns, expression length,
static-field/proxy/default-package/custom-map disallows, excluded node types) and
that a bypass of any on a default app is `VALID`. (§8.)
- **Q-jsm.** Confirm the OGNL Java Security Manager sandbox is **not** a relied-upon
control (opt-in, and non-functional on modern JDKs — see §5a), so a report premised
on its absence is not a finding. (§5a/§9.)
- **Q-dos.** Where is the line between "generic DoS we don't accept" and "super-linear
amplification inside framework code we do"? Confirm the §3/§8 wording. (§3.)
**Wave 3 — surfaces & false-friends**
- **Q-plugins.** Which bundled plugins (REST, JSON, Convention, …) are in scope at the
same request-trust level, and are any (e.g. REST/XML) historically higher-risk and
worth their own §8 note? (§2.)
- **Q-upload.** Confirm the multipart/file-upload surface (Jakarta) and what the
framework guarantees vs. leaves to the container/app. (§2/§6.)
- **Q12.** Beyond the `SECURITY.md` "Before Reporting" list already folded into §11a,
what do scanners/researchers most often report against Struts that you consider a
non-finding? (Feeds §11a.)
## §15 Appendix — existing-policy back-map
This `THREAT_MODEL.md` is **additive** — it does not replace
[`SECURITY.md`](SECURITY.md) (reporting process, supported versions, "Before
Reporting" checks) or the published [security guidance](https://struts.apache.org/security/);
both are preserved and remain canonical for the reporting workflow. The discoverability
chain is `AGENTS.md``SECURITY.md` → this model. Mapping of existing-policy claims to
sections:
| Existing-policy statement | Threat-model § |
| --- | --- |
| "Struts doesn't provide any security mechanism — pure web framework" | §1, §9, §13 (`BY-DESIGN`) |
| OGNL is the central historical vuln class | §1, §7, §8.1 |
| devMode / Config Browser Plugin are dev-only | §3, §5a, §11a |
| `@StrutsParameter` / unsafe setters | §6, §8.2, §10, §11a |
| Direct JSP access / raw `${}` EL / forced eval / localization | §3, §10, §11a |
| Allowlist / excluded classes/packages / expression length (7.0 defaults) | §5a, §8.1 |
| DMI / Strict Method Invocation | §5a, §8.3 |
| FetchMetadata / COOP / COEP | §5a, §8.5 |
| OGNL JSM sandbox (modern-JDK limitation) | §5a, §9 |
| Generic DoS not accepted; non-linear-in-input philosophy | §3, §8, §9 |
| "Before Reporting" duplicate/known-config checks | §3, §11a, §13 (`DUPLICATE`) |
| Supported versions (2.x EOL) | §5, §13 (`OUT-OF-MODEL: unsupported-version`) |
+4 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.3.0</version>
<version>7.1.1</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>struts2-apps</artifactId>
@@ -37,6 +37,9 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.site.skip>true</maven.site.skip>
<maven.site.deploy.skip>true</maven.site.deploy.skip>
</properties>
<build>
-18
View File
@@ -1,18 +0,0 @@
# Rest Showcase
> **WARNING:** This application is a demonstration/development tool only. It is **NOT** intended for production
> deployment. Deploying this application on a publicly accessible server may pose security risks.
Rest Showcase is a simple example of a REST app built with the REST plugin.
For more on getting started with Struts, see:
- https://struts.apache.org/getting-started/
## I18N
Please note that this project was created with the assumption that it will be run in an environment where the default
locale is set to English. This means that the default messages defined in `package.properties` are in English.
If the default locale for your server is different, then rename `package.properties` to `package_en.properties` and
create a new `package.properties` with proper values for your default locale.
+15
View File
@@ -0,0 +1,15 @@
README.txt - Rest Showcase Webapp
Rest Showcase is a simple example of REST app build with the REST plugin.
For more on getting started with Struts, see
* http://cwiki.apache.org/WW/home.html
I18N:
=====
Please note that this project was created with the assumption that it will be run
in an environment where the default locale is set to English. This means that
the default messages defined in package.properties are in English. If the default
locale for your server is different, then rename package.properties to package_en.properties
and create a new package.properties with proper values for your default locale.
+2 -10
View File
@@ -24,12 +24,12 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>7.3.0</version>
<version>7.1.1</version>
</parent>
<artifactId>struts2-rest-showcase</artifactId>
<packaging>war</packaging>
<version>7.3.0</version>
<version>7.1.1</version>
<name>Struts 2 Rest Showcase Webapp</name>
<description>Struts 2 Rest Showcase Example</description>
@@ -99,14 +99,6 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>bootstrap-icons</artifactId>
</dependency>
</dependencies>
<build>
@@ -31,7 +31,6 @@
<constant name="struts.convention.default.parent.package" value="rest-showcase"/>
<constant name="struts.convention.package.locators" value="example"/>
<constant name="struts.webjars.allowlist" value="bootstrap,bootstrap-icons"/>
<!-- Uncomment the lines below to use Jackson XML bindings instead of the XStream library to handle XML serialisations -->
<!--
@@ -29,8 +29,7 @@
<title>Orders</title>
<!-- Using a standard HTML link tag with JSP EL to get the contextPath may be simpler, but this is an equivalent for s:link -->
<s:set var="pageContextPath"><%=((HttpServletRequest)request).getContextPath()%></s:set>
<link rel="stylesheet" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
<link rel="stylesheet" href="<s:webjar path='bootstrap-icons/font/bootstrap-icons.min.css'/>"/>
<s:link href="%{#pageContextPath}/css/bootstrap.min.css" rel="stylesheet"></s:link>
<s:link href="%{#pageContextPath}/css/app.css" rel="stylesheet"></s:link>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
@@ -45,7 +44,7 @@
<div class="row">
<div class="col-md-12">
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Delete Order ${id}</h1>
</div>
@@ -60,7 +59,7 @@
</form>
<br />
<a href="${pageContext.request.contextPath}/orders" class="btn btn-info">
<i class="bi bi-arrow-left"></i> Back to Orders
<span class="glyphicon glyphicon-arrow-left"></span> Back to Orders
</a>
</div><!--/col-md-12--->
</div><!--/row-->
@@ -29,8 +29,7 @@
<title>Orders</title>
<!-- Using a standard HTML link tag with JSP EL to get the contextPath may be simpler, but this is an equivalent for s:link -->
<s:set var="pageContextPath"><%=((HttpServletRequest)request).getContextPath()%></s:set>
<link rel="stylesheet" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
<link rel="stylesheet" href="<s:webjar path='bootstrap-icons/font/bootstrap-icons.min.css'/>"/>
<s:link href="%{#pageContextPath}/css/bootstrap.min.css" rel="stylesheet"></s:link>
<s:link href="%{#pageContextPath}/css/app.css" rel="stylesheet"></s:link>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
@@ -45,22 +44,22 @@
<div class="row">
<div class="col-md-12">
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Order <s:property value="id" /></h1>
</div>
<s:actionmessage cssClass="alert alert-danger"/>
<s:form method="post" action="%{#request.contextPath}/orders/%{id}" theme="simple">
<s:form method="post" action="%{#request.contextPath}/orders/%{id}" cssClass="form-horizontal" theme="simple">
<s:hidden name="_method" value="put" />
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="id">ID</label>
<div class="form-group">
<label class="col-sm-2 control-label" for="id">ID</label>
<div class="col-sm-4">
<s:textfield id="id" name="id" disabled="true" cssClass="form-control"/>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="clientName">Client</label>
<div class="form-group">
<label class="col-sm-2 control-label" for="clientName">Client</label>
<div class="col-sm-4">
<s:textfield id="clientName" name="clientName" cssClass="form-control"/>
</div>
@@ -68,8 +67,8 @@
<s:fielderror fieldName="clientName" />
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="amount">Amount</label>
<div class="form-group">
<label class="col-sm-2 control-label" for="amount">Amount</label>
<div class="col-sm-4">
<s:textfield id="amount" name="amount" cssClass="form-control" />
</div>
@@ -77,14 +76,15 @@
<s:fielderror fieldName="amount" />
</div>
</div>
<div class="row mb-3">
<div class="offset-sm-2 col-sm-4">
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<s:submit cssClass="btn btn-primary"/>
</div>
</div>
<table>
</s:form>
<a href="${pageContext.request.contextPath}/orders" class="btn btn-info">
<i class="bi bi-arrow-left"></i> Back to Orders
<span class="glyphicon glyphicon-arrow-left"></span> Back to Orders
</a>
</div><!--/col-md-12--->
</div><!--/row-->
@@ -29,8 +29,7 @@
<title>Orders</title>
<!-- Using a standard HTML link tag with JSP EL to get the contextPath may be simpler, but this is an equivalent for s:link -->
<s:set var="pageContextPath"><%=((HttpServletRequest)request).getContextPath()%></s:set>
<link rel="stylesheet" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
<link rel="stylesheet" href="<s:webjar path='bootstrap-icons/font/bootstrap-icons.min.css'/>"/>
<s:link href="%{#pageContextPath}/css/bootstrap.min.css" rel="stylesheet"></s:link>
<s:link href="%{#pageContextPath}/css/app.css" rel="stylesheet"></s:link>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
@@ -45,15 +44,15 @@
<div class="row">
<div class="col-md-12">
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>New Order</h1>
</div>
<s:actionmessage cssClass="alert alert-danger"/>
<s:form method="post" action="%{#request.contextPath}/orders" theme="simple">
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="clientName">Client</label>
<s:form method="post" action="%{#request.contextPath}/orders" cssClass="form-horizontal" theme="simple">
<div class="form-group">
<label class="col-sm-2 control-label" for="clientName">Client</label>
<div class="col-sm-4">
<s:textfield id="clientName" name="clientName" cssClass="form-control"/>
</div>
@@ -61,8 +60,8 @@
<s:fielderror fieldName="clientName" />
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="amount">Amount</label>
<div class="form-group">
<label class="col-sm-2 control-label" for="amount">Amount</label>
<div class="col-sm-4">
<s:textfield id="amount" name="amount" cssClass="form-control"/>
</div>
@@ -70,14 +69,14 @@
<s:fielderror fieldName="amount" />
</div>
</div>
<div class="row mb-3">
<div class="offset-sm-2 col-sm-4">
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<s:submit cssClass="btn btn-primary"/>
</div>
</div>
</s:form>
<a href="${pageContext.request.contextPath}/orders" class="btn btn-info">
<i class="bi bi-arrow-left"></i> Back to Orders
<span class="glyphicon glyphicon-arrow-left"></apan> Back to Orders
</a>
</div><!--/col-md-12--->
</div><!--/row-->
@@ -29,8 +29,7 @@
<title>Orders</title>
<!-- Using a standard HTML link tag with JSP EL to get the contextPath may be simpler, but this is an equivalent for s:link -->
<s:set var="pageContextPath"><%=((HttpServletRequest)request).getContextPath()%></s:set>
<link rel="stylesheet" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
<link rel="stylesheet" href="<s:webjar path='bootstrap-icons/font/bootstrap-icons.min.css'/>"/>
<s:link href="%{#pageContextPath}/css/bootstrap.min.css" rel="stylesheet"></s:link>
<s:link href="%{#pageContextPath}/css/app.css" rel="stylesheet"></s:link>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
@@ -45,7 +44,7 @@
<div class="row">
<div class="col-md-12">
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Orders</h1>
</div>
<s:actionmessage cssClass="alert alert-danger"/>
@@ -63,15 +62,15 @@
<td><s:property value="amount"/></td>
<td>
<div class="btn-group">
<a href="orders/${id}" class="btn btn-secondary"><i class="bi bi-eye"></i> View</a>
<a href="orders/${id}/edit" class="btn btn-secondary"><i class="bi bi-pencil"></i> Edit</a>
<a href="orders/${id}/deleteConfirm" class="btn btn-danger"><i class="bi bi-trash"></i> Delete</a>
<a href="orders/${id}" class="btn btn-default"><span class="glyphicon glyphicon-eye-open"></span> View</a>
<a href="orders/${id}/edit" class="btn btn-default"><span class="glyphicon glyphicon-edit"></span> Edit</a>
<a href="orders/${id}/deleteConfirm" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> Delete</a>
</div>
</td>
</tr>
</s:iterator>
</table>
<a href="orders/new" class="btn btn-primary"><i class="bi bi-file-earmark"></i> Create a new order</a>
<a href="orders/new" class="btn btn-primary"><span class="glyphicon glyphicon-file"></span> Create a new order</a>
</div><!--/col-md-12--->
</div><!--/row-->
</div><!--/container-->
@@ -29,8 +29,7 @@
<title>Orders</title>
<!-- Using a standard HTML link tag with JSP EL to get the contextPath may be simpler, but this is an equivalent for s:link -->
<s:set var="pageContextPath"><%=((HttpServletRequest)request).getContextPath()%></s:set>
<link rel="stylesheet" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
<link rel="stylesheet" href="<s:webjar path='bootstrap-icons/font/bootstrap-icons.min.css'/>"/>
<s:link href="%{#pageContextPath}/css/bootstrap.min.css" rel="stylesheet"></s:link>
<s:link href="%{#pageContextPath}/css/app.css" rel="stylesheet"></s:link>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
@@ -44,25 +43,25 @@
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Order ${id}</h1>
</div>
<table class="table table-striped">
<tr>
<td class="col-3">ID</td>
<td class="col-9"><s:property value="id"/></td>
<td class="span3">ID</td>
<td class="span9"><s:property value="id"/></td>
</tr>
<tr>
<td class="col-3">Client</td>
<td class="col-9"><s:property value="clientName"/></td>
<td class="span3">Client</td>
<td class="span9"><s:property value="clientName"/></td>
</tr>
<tr>
<td class="col-3">Amount</td>
<td class="col-9"><s:property value="amount"/></td>
<td class="span3">Amount</td>
<td class="span9"><s:property value="amount"/></td>
</tr>
</table>
<a href="${pageContext.request.contextPath}/orders" class="btn btn-info">
<i class="bi bi-arrow-left"></i> Back to Orders
<span class="glyphicon glyphicon-arrow-left"></span> Back to Orders
</a>
</div><!--/col-md-12--->
</div><!--/row-->
@@ -0,0 +1,476 @@
/*!
* Bootstrap v3.3.4 (http://getbootstrap.com)
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
.btn-default,
.btn-primary,
.btn-success,
.btn-info,
.btn-warning,
.btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
}
.btn-default:active,
.btn-primary:active,
.btn-success:active,
.btn-info:active,
.btn-warning:active,
.btn-danger:active,
.btn-default.active,
.btn-primary.active,
.btn-success.active,
.btn-info.active,
.btn-warning.active,
.btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
}
.btn-default .badge,
.btn-primary .badge,
.btn-success .badge,
.btn-info .badge,
.btn-warning .badge,
.btn-danger .badge {
text-shadow: none;
}
.btn:active,
.btn.active {
background-image: none;
}
.btn-default {
text-shadow: 0 1px 0 #fff;
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #dbdbdb;
border-color: #ccc;
}
.btn-default:hover,
.btn-default:focus {
background-color: #e0e0e0;
background-position: 0 -15px;
}
.btn-default:active,
.btn-default.active {
background-color: #e0e0e0;
border-color: #dbdbdb;
}
.btn-default.disabled,
.btn-default:disabled,
.btn-default[disabled] {
background-color: #e0e0e0;
background-image: none;
}
.btn-primary {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #245580;
}
.btn-primary:hover,
.btn-primary:focus {
background-color: #265a88;
background-position: 0 -15px;
}
.btn-primary:active,
.btn-primary.active {
background-color: #265a88;
border-color: #245580;
}
.btn-primary.disabled,
.btn-primary:disabled,
.btn-primary[disabled] {
background-color: #265a88;
background-image: none;
}
.btn-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #3e8f3e;
}
.btn-success:hover,
.btn-success:focus {
background-color: #419641;
background-position: 0 -15px;
}
.btn-success:active,
.btn-success.active {
background-color: #419641;
border-color: #3e8f3e;
}
.btn-success.disabled,
.btn-success:disabled,
.btn-success[disabled] {
background-color: #419641;
background-image: none;
}
.btn-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #28a4c9;
}
.btn-info:hover,
.btn-info:focus {
background-color: #2aabd2;
background-position: 0 -15px;
}
.btn-info:active,
.btn-info.active {
background-color: #2aabd2;
border-color: #28a4c9;
}
.btn-info.disabled,
.btn-info:disabled,
.btn-info[disabled] {
background-color: #2aabd2;
background-image: none;
}
.btn-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #e38d13;
}
.btn-warning:hover,
.btn-warning:focus {
background-color: #eb9316;
background-position: 0 -15px;
}
.btn-warning:active,
.btn-warning.active {
background-color: #eb9316;
border-color: #e38d13;
}
.btn-warning.disabled,
.btn-warning:disabled,
.btn-warning[disabled] {
background-color: #eb9316;
background-image: none;
}
.btn-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #b92c28;
}
.btn-danger:hover,
.btn-danger:focus {
background-color: #c12e2a;
background-position: 0 -15px;
}
.btn-danger:active,
.btn-danger.active {
background-color: #c12e2a;
border-color: #b92c28;
}
.btn-danger.disabled,
.btn-danger:disabled,
.btn-danger[disabled] {
background-color: #c12e2a;
background-image: none;
}
.thumbnail,
.img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-color: #e8e8e8;
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
background-color: #2e6da4;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
.navbar-default {
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
}
.navbar-default .navbar-nav > .open > a,
.navbar-default .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
}
.navbar-brand,
.navbar-nav > li > a {
text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
}
.navbar-inverse {
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
}
.navbar-inverse .navbar-nav > .open > a,
.navbar-inverse .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
}
.navbar-inverse .navbar-brand,
.navbar-inverse .navbar-nav > li > a {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
}
.navbar-static-top,
.navbar-fixed-top,
.navbar-fixed-bottom {
border-radius: 0;
}
@media (max-width: 767px) {
.navbar .navbar-nav .open .dropdown-menu > .active > a,
.navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
color: #fff;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
}
.alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
}
.alert-success {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
background-repeat: repeat-x;
border-color: #b2dba1;
}
.alert-info {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
background-repeat: repeat-x;
border-color: #9acfea;
}
.alert-warning {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
background-repeat: repeat-x;
border-color: #f5e79e;
}
.alert-danger {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
background-repeat: repeat-x;
border-color: #dca7a7;
}
.progress {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-striped {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
}
.list-group {
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.list-group-item.active,
.list-group-item.active:hover,
.list-group-item.active:focus {
text-shadow: 0 -1px 0 #286090;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
background-repeat: repeat-x;
border-color: #2b669a;
}
.list-group-item.active .badge,
.list-group-item.active:hover .badge,
.list-group-item.active:focus .badge {
text-shadow: none;
}
.panel {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
}
.panel-default > .panel-heading {
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.panel-primary > .panel-heading {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
.panel-success > .panel-heading {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
background-repeat: repeat-x;
}
.panel-info > .panel-heading {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
background-repeat: repeat-x;
}
.panel-warning > .panel-heading {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
background-repeat: repeat-x;
}
.panel-danger > .panel-heading {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
background-repeat: repeat-x;
}
.well {
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
border-color: #dcdcdc;
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
}
/*# sourceMappingURL=bootstrap-theme.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-19
View File
@@ -1,19 +0,0 @@
# Showcase
> **WARNING:** This application is a demonstration/development tool only. It is **NOT** intended for production
> deployment. It contains features such as source code viewing that intentionally expose internal application details.
> Deploying this application on a publicly accessible server may pose security risks.
Showcase is a collection of examples with code that you might adopt and adapt in your own applications.
For more on getting started with Struts, see:
- https://struts.apache.org/getting-started/
## I18N
Please note that this project was created with the assumption that it will be run in an environment where the default
locale is set to English. This means that the default messages defined in `package.properties` are in English.
If the default locale for your server is different, then rename `package.properties` to `package_en.properties` and
create a new `package.properties` with proper values for your default locale.
+16
View File
@@ -0,0 +1,16 @@
README.txt - showcase
Showcase is a collection of examples with code that you might be adopt and
adapt in your own applications.
For more on getting started with Struts, see
* http://cwiki.apache.org/WW/home.html
I18N:
=====
Please note that this project was created with the assumption that it will be run
in an environment where the default locale is set to English. This means that
the default messages defined in package.properties are in English. If the default
locale for your server is different, then rename package.properties to package_en.properties
and create a new package.properties with proper values for your default locale.
+4 -21
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>7.3.0</version>
<version>7.1.1</version>
</parent>
<artifactId>struts2-showcase</artifactId>
@@ -119,15 +119,11 @@
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
</dependency>
<dependency>
<groupId>org.sitemesh</groupId>
<artifactId>sitemesh</artifactId>
<version>3.2.3</version>
<version>3.2.2</version>
</dependency>
<dependency>
@@ -170,20 +166,7 @@
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-jsr223</artifactId>
<version>3.0.25</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>bootstrap-icons</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>${webjars-jquery-showcase.version}</version>
<version>3.0.22</version>
</dependency>
</dependencies>
@@ -224,7 +207,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.6</version>
<version>3.5.4</version>
<configuration>
<includes>
<include>it.org.apache.struts2.showcase.*Test</include>
@@ -1,32 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.showcase.action;
import org.apache.struts2.ActionSupport;
public class Html5Action extends ActionSupport {
@Override
public String execute() throws Exception {
addActionError("Action error: only html5");
addActionMessage("Action message: only html5");
addFieldError("testField", "Field error: only html5");
return super.execute();
}
}
@@ -1,225 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.showcase.fileupload;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ActionSupport;
import org.apache.struts2.Preparable;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.action.UploadedFilesAware;
import org.apache.struts2.dispatcher.multipart.UploadedFile;
import org.apache.struts2.interceptor.parameter.StrutsParameter;
import java.util.List;
/**
* <p>
* Demonstrates dynamic file upload validation using WithLazyParams.
* This action shows how file upload validation rules can be determined
* at runtime based on action properties, session data, or other dynamic values.
* </p>
*
* <p>
* The validation parameters (allowedTypes, allowedExtensions, maximumSize)
* are set dynamically in the prepare() method and then referenced in struts.xml
* using ${...} expressions. This allows the same action to enforce different
* validation rules based on runtime conditions.
* </p>
*
* <p>
* This example demonstrates two use cases:
* </p>
* <ul>
* <li><strong>Document Upload:</strong> Accepts PDF and Word documents up to 5MB</li>
* <li><strong>Image Upload:</strong> Accepts JPEG and PNG images up to 2MB</li>
* </ul>
*
* @see org.apache.struts2.interceptor.WithLazyParams
* @see org.apache.struts2.interceptor.ActionFileUploadInterceptor
*/
public class DynamicFileUploadAction extends ActionSupport implements Preparable, UploadedFilesAware {
private static final Logger LOG = LogManager.getLogger(DynamicFileUploadAction.class);
private UploadedFile uploadedFile;
private String contentType;
private String fileName;
private String originalName;
private String inputName;
private String uploadType = "document";
private transient UploadConfig uploadConfig;
@Override
public String input() {
return INPUT;
}
public String upload() {
if (uploadedFile == null) {
addActionError("Please select a file to upload");
return INPUT;
}
return SUCCESS;
}
@Override
public void withUploadedFiles(List<UploadedFile> uploadedFiles) {
if (!uploadedFiles.isEmpty()) {
LOG.info("Uploaded file: {}", uploadedFiles.get(0));
this.uploadedFile = uploadedFiles.get(0);
this.fileName = uploadedFile.getName();
this.contentType = uploadedFile.getContentType();
this.originalName = uploadedFile.getOriginalName();
this.inputName = uploadedFile.getInputName();
}
}
// Getters and Setters
public String getContentType() {
return contentType;
}
public String getFileName() {
return fileName;
}
public String getOriginalName() {
return originalName;
}
public String getInputName() {
return inputName;
}
public Object getUploadedFile() {
return uploadedFile != null ? uploadedFile.getContent() : null;
}
public long getUploadSize() {
return uploadedFile != null ? uploadedFile.length() : 0;
}
public String getUploadType() {
return uploadType;
}
@StrutsParameter
public void setUploadType(String uploadType) {
this.uploadType = uploadType;
}
@Override
public void prepare() throws Exception {
// no-op
}
public void prepareUpload() {
String type = ServletActionContext.getRequest().getParameter("uploadType");
prepareUploadConfig(type != null ? type : "document");
}
private void prepareUploadConfig(String uploadType) {
uploadConfig = new UploadConfig();
LOG.debug("Configure validation rules based on upload type: {}", uploadType);
if ("image".equals(uploadType)) {
// Image upload configuration
uploadConfig.setAllowedMimeTypes("image/jpeg,image/png");
uploadConfig.setAllowedExtensions(".jpg,.jpeg,.png");
uploadConfig.setMaxFileSize(2097152L); // 2MB
uploadConfig.setDescription("images (JPEG, PNG)");
} else {
// Document upload configuration (default)
uploadConfig.setAllowedMimeTypes("application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document");
uploadConfig.setAllowedExtensions(".pdf,.doc,.docx");
uploadConfig.setMaxFileSize(5242880L); // 5MB
uploadConfig.setDescription("documents (PDF, Word)");
}
}
/**
* Returns the upload configuration object.
* This is used in struts.xml with ${uploadConfig.allowedMimeTypes} expressions.
*/
public UploadConfig getUploadConfig() {
return uploadConfig;
}
/**
* Configuration holder for dynamic file upload validation rules.
*/
public static class UploadConfig {
private String allowedMimeTypes;
private String allowedExtensions;
private Long maxFileSize;
private String description;
public String getAllowedMimeTypes() {
return allowedMimeTypes;
}
public void setAllowedMimeTypes(String allowedMimeTypes) {
this.allowedMimeTypes = allowedMimeTypes;
}
public String getAllowedExtensions() {
return allowedExtensions;
}
public void setAllowedExtensions(String allowedExtensions) {
this.allowedExtensions = allowedExtensions;
}
public Long getMaxFileSize() {
return maxFileSize;
}
public void setMaxFileSize(Long maxFileSize) {
this.maxFileSize = maxFileSize;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
/**
* Returns a human-readable string representation of the max file size.
*/
public String getMaxFileSizeFormatted() {
if (maxFileSize == null) {
return "unlimited";
}
if (maxFileSize < 1024) {
return maxFileSize + " bytes";
} else if (maxFileSize < 1024 * 1024) {
return (maxFileSize / 1024) + " KB";
} else {
return (maxFileSize / (1024 * 1024)) + " MB";
}
}
}
}
@@ -1,42 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.showcase.proxy;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Simple AOP interceptor that wraps actions in a Spring proxy.
* Used to test that Struts correctly handles Spring AOP proxied actions
* in action chaining scenarios (WW-5514).
*/
public class LoggingInterceptor implements MethodInterceptor {
private static final Logger LOG = LogManager.getLogger(LoggingInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
LOG.debug("Invoking method: {} on target: {}",
invocation.getMethod().getName(),
invocation.getThis().getClass().getName());
return invocation.proceed();
}
}
@@ -30,9 +30,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@@ -92,11 +90,7 @@ public class ViewSourceAction extends ActionSupport implements ServletContextAwa
if (config != null && config.startsWith("file:/")) {
int pos = config.lastIndexOf(':');
configLine = Integer.parseInt(config.substring(pos + 1));
String fileUrl = config.substring(0, pos);
Path configPath = resolveAllowedConfigPath(fileUrl);
if (configPath != null) {
configLines = read(Files.newInputStream(configPath), configLine);
}
configLines = read(new URL(config.substring(0, pos)).openStream(), configLine);
}
return SUCCESS;
}
@@ -233,29 +227,6 @@ public class ViewSourceAction extends ActionSupport implements ServletContextAwa
return snippet;
}
/**
* Resolves the given file URL to a real path if it points to an XML file within the webapp's
* deployment directory, preventing arbitrary file reads via crafted config parameters.
*
* @return the resolved path if allowed, or null if the path is outside the webapp or not an XML file
*/
private Path resolveAllowedConfigPath(String fileUrl) {
try {
Path filePath = Path.of(new URI(fileUrl)).toRealPath();
String realBasePath = servletContext.getRealPath("/");
if (realBasePath == null) {
return null;
}
Path basePath = Path.of(realBasePath).toRealPath();
if (filePath.startsWith(basePath) && filePath.toString().endsWith(".xml")) {
return filePath;
}
return null;
} catch (Exception e) {
return null;
}
}
@Override
public void withServletContext(ServletContext arg0) {
this.servletContext = arg0;
@@ -29,11 +29,6 @@
<Root level="info">
<AppenderRef ref="STDOUT"/>
</Root>
<!--
<Logger name="org.apache.struts2" level="info"/>
<Logger name="org.apache.struts2.showcase.fileupload" level="debug"/>
<Logger name="org.apache.struts2.inject" level="debug"/>
<Logger name="org.apache.struts2.interceptor.ActionFileUploadInterceptor" level="debug"/>
-->
</Loggers>
</Configuration>
@@ -20,26 +20,21 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="actionchaining" extends="struts-default" namespace="/actionchaining">
<action name="actionChain1" class="org.apache.struts2.showcase.actionchaining.ActionChain1">
<result type="chain">actionChain2</result>
</action>
<action name="actionChain2" class="org.apache.struts2.showcase.actionchaining.ActionChain2">
<result type="chain">actionChain3</result>
</action>
<action name="actionChain3" class="org.apache.struts2.showcase.actionchaining.ActionChain3">
<result>/WEB-INF/actionchaining/actionChainingResult.jsp</result>
</action>
<!-- Spring AOP Proxied Action Chain Test (WW-5514) -->
<action name="proxiedActionChain1" class="proxiedActionChain1">
<result type="chain">actionChain2</result>
</action>
</package>
<package name="actionchaining" extends="struts-default" namespace="/actionchaining">
<action name="actionChain1" class="org.apache.struts2.showcase.actionchaining.ActionChain1">
<result type="chain">actionChain2</result>
</action>
<action name="actionChain2" class="org.apache.struts2.showcase.actionchaining.ActionChain2">
<result type="chain">actionChain3</result>
</action>
<action name="actionChain3" class="org.apache.struts2.showcase.actionchaining.ActionChain3">
<result>/WEB-INF/actionchaining/actionChainingResult.jsp</result>
</action>
</package>
</struts>
@@ -20,66 +20,43 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<constant name="struts.multipart.maxSize" value="10240"/>
<constant name="struts.multipart.maxSize" value="10240" />
<package name="fileupload" extends="struts-default" namespace="/fileupload">
<package name="fileupload" extends="struts-default" namespace="/fileupload">
<action name="upload" class="org.apache.struts2.showcase.fileupload.FileUploadAction" method="input">
<result>/WEB-INF/fileupload/upload.jsp</result>
</action>
<result>/WEB-INF/fileupload/upload.jsp</result>
</action>
<action name="doUpload" class="org.apache.struts2.showcase.fileupload.FileUploadAction" method="upload">
<result name="input">/WEB-INF/fileupload/upload.jsp</result>
<result>/WEB-INF/fileupload/upload-success.jsp</result>
</action>
<result name="input">/WEB-INF/fileupload/upload.jsp</result>
<result>/WEB-INF/fileupload/upload-success.jsp</result>
</action>
<action name="multipleUploadUsingList">
<result>/WEB-INF/fileupload/multipleUploadUsingList.jsp</result>
</action>
<action name="multipleUploadUsingList">
<result>/WEB-INF/fileupload/multipleUploadUsingList.jsp</result>
</action>
<action name="doMultipleUploadUsingList"
class="org.apache.struts2.showcase.fileupload.MultipleFileUploadUsingListAction" method="upload">
<result name="input">/WEB-INF/fileupload/multipleUploadUsingList.jsp</result>
<result>/WEB-INF/fileupload/multiple-success.jsp</result>
</action>
<action name="doMultipleUploadUsingList" class="org.apache.struts2.showcase.fileupload.MultipleFileUploadUsingListAction" method="upload">
<result name="input">/WEB-INF/fileupload/multipleUploadUsingList.jsp</result>
<result>/WEB-INF/fileupload/multiple-success.jsp</result>
</action>
<action name="multipleUploadUsingArray">
<result>/WEB-INF/fileupload/multipleUploadUsingArray.jsp</result>
</action>
<action name="multipleUploadUsingArray">
<result>/WEB-INF/fileupload/multipleUploadUsingArray.jsp</result>
</action>
<action name="doMultipleUploadUsingArray"
class="org.apache.struts2.showcase.fileupload.MultipleFileUploadUsingArrayAction" method="upload">
<result name="input">/WEB-INF/fileupload/multipleUploadUsingArray.jsp</result>
<result>/WEB-INF/fileupload/multiple-success.jsp</result>
</action>
<action name="doMultipleUploadUsingArray" class="org.apache.struts2.showcase.fileupload.MultipleFileUploadUsingArrayAction" method="upload">
<result name="input">/WEB-INF/fileupload/multipleUploadUsingArray.jsp</result>
<result>/WEB-INF/fileupload/multiple-success.jsp</result>
</action>
<!-- Dynamic File Upload with WithLazyParams -->
<action name="dynamicUpload" class="org.apache.struts2.showcase.fileupload.DynamicFileUploadAction"
method="input">
<result name="input">/WEB-INF/fileupload/dynamic-upload.jsp</result>
</action>
<action name="doDynamicUpload" class="org.apache.struts2.showcase.fileupload.DynamicFileUploadAction"
method="upload">
<!--
WithLazyParams allows dynamic parameter evaluation.
The ${...} expressions are evaluated at runtime from the ValueStack,
allowing validation rules to be determined by action state.
-->
<interceptor-ref name="defaultStack">
<param name="actionFileUpload.allowedTypes">${uploadConfig.allowedMimeTypes}</param>
<param name="actionFileUpload.allowedExtensions">${uploadConfig.allowedExtensions}</param>
<param name="actionFileUpload.maximumSize">${uploadConfig.maxFileSize}</param>
</interceptor-ref>
<result name="input">/WEB-INF/fileupload/dynamic-upload.jsp</result>
<result>/WEB-INF/fileupload/dynamic-upload-success.jsp</result>
</action>
</package>
</struts>
+51 -58
View File
@@ -20,89 +20,90 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<!-- START SNIPPET: xworkSample -->
<struts>
<!-- Some or all of these can be flipped to true for debugging -->
<constant name="struts.i18n.reload" value="false"/>
<constant name="struts.enable.DynamicMethodInvocation" value="true"/>
<constant name="struts.devMode" value="false"/>
<constant name="struts.configuration.xml.reload" value="false"/>
<constant name="struts.custom.i18n.resources" value="globalMessages"/>
<constant name="struts.action.extension" value="action,,"/>
<constant name="struts.i18n.reload" value="false" />
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
<constant name="struts.devMode" value="false" />
<constant name="struts.configuration.xml.reload" value="false" />
<constant name="struts.custom.i18n.resources" value="globalMessages" />
<constant name="struts.action.extension" value="action,," />
<constant name="struts.allowlist.enable" value="true"/>
<constant name="struts.parameters.requireAnnotations" value="true"/>
<constant name="struts.allowlist.packageNames" value="org.apache.struts2.showcase"/>
<constant name="struts.webjars.allowlist" value="jquery,bootstrap,bootstrap-icons"/>
<constant name="struts.allowlist.enable" value="true" />
<constant name="struts.parameters.requireAnnotations" value="true" />
<constant name="struts.allowlist.packageNames" value="
org.apache.struts2.showcase.model,
org.apache.struts2.showcase.modelDriven.model
"/>
<constant name="struts.allowlist.classes" value="
org.apache.struts2.showcase.hangman.Hangman,
org.apache.struts2.showcase.hangman.Vocab
"/>
<!-- Enable Spring AOP proxy support for action chaining test (WW-5514) -->
<constant name="struts.disallowProxyObjectAccess" value="false"/>
<constant name="struts.convention.package.locators.basePackage" value="org.apache.struts2.showcase"/>
<constant name="struts.convention.result.path" value="/WEB-INF"/>
<constant name="struts.convention.package.locators.basePackage" value="org.apache.struts2.showcase" />
<constant name="struts.convention.result.path" value="/WEB-INF" />
<!-- Necessary for Showcase because default includes org.apache.struts2.* -->
<constant name="struts.convention.exclude.packages"
value="org.apache.struts.*,org.springframework.web.struts.*,org.springframework.web.struts2.*,org.hibernate.*"/>
<constant name="struts.convention.exclude.packages" value="org.apache.struts.*,org.springframework.web.struts.*,org.springframework.web.struts2.*,org.hibernate.*"/>
<constant name="struts.freemarker.manager.classname" value="customFreemarkerManager"/>
<constant name="struts.serve.static" value="true"/>
<constant name="struts.serve.static.browserCache" value="false"/>
<constant name="struts.freemarker.manager.classname" value="customFreemarkerManager" />
<constant name="struts.serve.static" value="true" />
<constant name="struts.serve.static.browserCache" value="false" />
<constant name="struts.action.excludePattern"
value=".*/images/.*\.gif,.*/img/.*\.gif,/testServlet/.*"/>
<constant name="struts.action.excludePattern" value=".*/images/.*\.gif,.*/img/.*\.gif,.*/styles/.*\.css,.*/js/.*\.js,/testServlet/.*"/>
<include file="struts-interactive.xml"/>
<include file="struts-interactive.xml" />
<include file="struts-hangman.xml"/>
<include file="struts-hangman.xml" />
<include file="struts-tags.xml"/>
<include file="struts-validation.xml"/>
<include file="struts-validation.xml" />
<include file="struts-actionchaining.xml"/>
<include file="struts-actionchaining.xml" />
<include file="struts-fileupload.xml"/>
<include file="struts-fileupload.xml" />
<include file="struts-person.xml"/>
<include file="struts-person.xml" />
<include file="struts-wait.xml"/>
<include file="struts-wait.xml" />
<include file="struts-token.xml"/>
<include file="struts-token.xml" />
<include file="struts-model-driven.xml"/>
<include file="struts-model-driven.xml" />
<include file="struts-filedownload.xml"/>
<include file="struts-filedownload.xml" />
<include file="struts-conversion.xml"/>
<include file="struts-conversion.xml" />
<include file="struts-freemarker.xml"/>
<include file="struts-freemarker.xml" />
<include file="struts-tiles.xml"/>
<include file="struts-tiles.xml" />
<include file="struts-xslt.xml"/>
<include file="struts-xslt.xml" />
<include file="struts-async.xml"/>
<include file="struts-async.xml" />
<include file="struts-dispatcher.xml"/>
<include file="struts-dispatcher.xml" />
<include file="struts-params-annotation.xml"/>
<include file="struts-params-annotation.xml" />
<package name="default" extends="struts-default">
<interceptors>
<interceptor-stack name="crudStack">
<interceptor-ref name="checkbox"/>
<interceptor-ref name="params"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="checkbox" />
<interceptor-ref name="params" />
<interceptor-ref name="staticParams" />
<interceptor-ref name="defaultStack" />
</interceptor-stack>
</interceptors>
<default-action-ref name="showcase"/>
<default-action-ref name="showcase" />
<action name="showcase">
<result>/WEB-INF/showcase.jsp</result>
@@ -131,7 +132,7 @@
</action>
<action name="edit" class="org.apache.struts2.showcase.action.SkillAction">
<result>/WEB-INF/empmanager/editSkill.jsp</result>
<interceptor-ref name="params"/>
<interceptor-ref name="params" />
<interceptor-ref name="basicStack"/>
</action>
<action name="save" class="org.apache.struts2.showcase.action.SkillAction" method="save">
@@ -152,11 +153,9 @@
<interceptor-ref name="basicStack"/>
</action>
<action name="edit-*" class="org.apache.struts2.showcase.action.EmployeeAction">
<param name="empId">{1}</param>
<param name="empId">{1}</param>
<result>/WEB-INF/empmanager/editEmployee.jsp</result>
<interceptor-ref name="crudStack">
<param name="validation.excludeMethods">execute</param>
</interceptor-ref>
<interceptor-ref name="crudStack"><param name="validation.excludeMethods">execute</param></interceptor-ref>
</action>
<action name="save" class="org.apache.struts2.showcase.action.EmployeeAction" method="save">
<result name="input">/WEB-INF/empmanager/editEmployee.jsp</result>
@@ -168,13 +167,7 @@
</action>
</package>
<package name="html5" extends="default" namespace="/html5">
<action name="index" class="org.apache.struts2.showcase.action.Html5Action">
<result>/WEB-INF/html5/index.jsp</result>
</action>
</package>
</struts>
<!-- END SNIPPET: xworkSample -->
<!-- END SNIPPET: xworkSample -->
@@ -25,7 +25,7 @@
<title>Struts2 Showcase - Action Chaining Result</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Action Chaining Result:</h1>
</div>
@@ -115,25 +115,5 @@
<bean id="guessCharacterAction" class="org.apache.struts2.showcase.hangman.GuessCharacterAction" scope="prototype"/>
<bean id="getUpdatedHangmanAction" class="org.apache.struts2.showcase.hangman.GetUpdatedHangmanAction"
scope="prototype"/>
<!-- Spring AOP Proxy Configuration for Action Chaining Test (WW-5514) -->
<bean id="loggingInterceptor" class="org.apache.struts2.showcase.proxy.LoggingInterceptor"/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="proxyTargetClass" value="true"/>
<property name="beanNames">
<list>
<value>proxiedActionChain1</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>loggingInterceptor</value>
</list>
</property>
</bean>
<bean id="proxiedActionChain1" class="org.apache.struts2.showcase.actionchaining.ActionChain1" scope="prototype"/>
</beans>
@@ -26,7 +26,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Bean Validation Examples</h1>
</div>
@@ -24,7 +24,7 @@
<title>Struts2 Showcase - Conversion - Populate into Struts action class a Set of Address.java Object</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Conversion - Populate into Struts action class a Set of Address.java Object</h1>
</div>
@@ -24,7 +24,7 @@
<title>Struts2 Showcase - Conversion - Tiger 5 Enum</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Conversion - Tiger 5 Enum</h1>
</div>
@@ -24,7 +24,7 @@
<title>Struts2 Showcase - Conversion - Populate Object into Struts' action List</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Conversion - Populate Object into Struts' action List</h1>
</div>
@@ -43,8 +43,8 @@
<p/>
<s:actionerror cssClass="alert alert-danger"/>
<s:fielderror cssClass="alert alert-danger"/>
<s:actionerror cssClass="alert alert-error"/>
<s:fielderror cssClass="alert alert-error"/>
<s:form action="submitPersonInfo" namespace="/conversion" method="post">
<%--
@@ -25,7 +25,7 @@
<title>Struts2 Showcase - Conversion</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Conversion</h1>
</div>
@@ -24,7 +24,7 @@
<title>Struts2 Showcase - Conversion - Populate into Struts action class a Set of Address.java Object</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Conversion - Populate into Struts action class a Set of Address.java Object</h1>
</div>
@@ -24,7 +24,7 @@
<title>Struts2 Showcase - Conversion - Tiger 5 Enum</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Conversion - Tiger 5 Enum</h1>
</div>
@@ -24,7 +24,7 @@
<title>Struts2 Showcase - Conversion - Populate Object into Struts' action List</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Conversion - Populate Object into Struts' action List</h1>
</div>
@@ -61,26 +61,20 @@
<title><sitemesh:write property="title"/></title>
<link rel="stylesheet" type="text/css" media="all" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
<link rel="stylesheet" type="text/css" href="<s:webjar path='bootstrap-icons/font/bootstrap-icons.min.css'/>"/>
<s:url var="bootstrapCss" value='/styles/bootstrap.css' encode='false' includeParams='none'/>
<s:link href="%{bootstrapCss}" rel="stylesheet" type="text/css" media="all"/>
<s:url var="mainCss" value='/styles/main.css' encode='false' includeParams='none'/>
<s:link href="%{mainCss}" rel="stylesheet" type="text/css" media="all"/>
<script src="<s:webjar path='jquery/jquery.min.js'/>"></script>
<script defer src="<s:webjar path='bootstrap/js/bootstrap.bundle.min.js'/>"></script>
<s:url var="jqueryJs" value='/js/jquery-2.1.4.min.js' encode='false' includeParams='none'/>
<s:script src="%{jqueryJs}"/>
<s:url var="bootstrapJs" value='/js/bootstrap.min.js' encode='false' includeParams='none'/>
<s:script src="%{bootstrapJs}"/>
<s:script>
$(function () {
$('ul.alert').each(function () {
var ul = $(this);
// Move the alert* classes (base + variant, e.g. alert-danger) onto a
// dismissible wrapper so it becomes the single alert container, then
// strip them from the <ul> to avoid a nested, uncoloured alert box.
var alertClasses = (ul.attr('class').match(/\balert\S*/g) || []).join(' ');
ul.removeClass(alertClasses);
var wrapper = $('<div class="alert-dismissible" />').addClass(alertClasses);
ul.before(wrapper);
wrapper.append('<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>').append(ul);
});
var alerts = $('ul.alert').wrap('<div />');
alerts.prepend('<a class="close" data-dismiss="alert" href="#">&times;</a>');
alerts.alert();
});
</s:script>
@@ -103,107 +97,114 @@
<body id="page-home">
<nav class="navbar navbar-expand-lg bg-light fixed-top" data-bs-theme="light">
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
<s:url var="home" action="showcase" namespace="/" includeContext="false" />
<s:a value="%{home}" cssClass="navbar-brand">
Struts2 Showcase
</s:a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-main" aria-controls="navbar-main" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<s:url var="home" action="showcase" namespace="/" includeContext="false" />
<s:a value="%{home}" cssClass="navbar-brand">
Struts2 Showcase
</s:a>
</div>
<div class="collapse navbar-collapse" id="navbar-main">
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="nav-item"><s:a value="%{home}" cssClass="nav-link"><i class="bi bi-house"></i> Home</s:a></li>
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">
<i class="bi bi-gear"></i> Configuration</a>
<li><s:a value="%{home}"><i class="glyphicon glyphicon-home"></i> Home</s:a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="glyphicon glyphicon-cog"></i> Configuration
<b class="caret"></b></a>
<ul class="dropdown-menu" role="menu">
<li><s:a action="actionChain1!input" namespace="/actionchaining"
includeParams="none" cssClass="dropdown-item">Action Chaining</s:a></li>
includeParams="none">Action Chaining</s:a></li>
<li><s:a action="index" namespace="/config-browser"
includeParams="none" cssClass="dropdown-item">Config Browser</s:a></li>
includeParams="none">Config Browser</s:a></li>
<s:url var="conversion" action="index" namespace="/conversion" includeContext="false" />
<li><s:a value="%{conversion}" cssClass="dropdown-item">Conversion</s:a></li>
<li><s:a value="/person/index.html" cssClass="dropdown-item">Person Manager ( by Conventions )</s:a></li>
<li><s:a value="%{conversion}">Conversion</s:a></li>
<li><s:a value="/person/index.html">Person Manager ( by Conventions )</s:a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">Non UI Tags</a>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Non UI Tags<b class="caret"></b></a>
<ul class="dropdown-menu" role="menu">
<li><s:url var="url" action="showActionTagDemo" namespace="/tags/non-ui/actionTag"/>
<s:a href="%{url}" cssClass="dropdown-item">Action Tag</s:a></li>
<li><s:url var="url" namespace="/tags/non-ui" action="date"/>
<s:a href="%{url}" cssClass="dropdown-item">Date Tag</s:a></li>
<li><s:url var="url" action="debugTagDemo" namespace="/tags/non-ui"/>
<s:a href="%{url}" cssClass="dropdown-item">Debug Tag</s:a></li>
<li><s:url var="url" action="showGeneratorTagDemo" namespace="/tags/non-ui/iteratorGeneratorTag"/>
<s:a href="%{url}" cssClass="dropdown-item">Iterator Generator Tag</s:a></li>
<li><s:url var="url" action="showActionTagDemo" namespace="/tags/non-ui/actionTag"/><s:a
href="%{url}">Action Tag</s:a></li>
<li><s:url var="url" namespace="/tags/non-ui" action="date"/><s:a
href="%{url}">Date Tag</s:a></li>
<li><s:url var="url" action="debugTagDemo" namespace="/tags/non-ui"/><s:a
href="%{url}">Debug Tag</s:a></li>
<li><s:url var="url" action="showGeneratorTagDemo"
namespace="/tags/non-ui/iteratorGeneratorTag"/><s:a
href="%{url}">Iterator Generator Tag</s:a></li>
<li>
<s:url var="url" action="showAppendTagDemo" namespace="/tags/non-ui/appendIteratorTag"/>
<s:a href="%{#url}" cssClass="dropdown-item">Append Iterator Tag</s:a>
<s:url var="url" action="showAppendTagDemo"
namespace="/tags/non-ui/appendIteratorTag"/>
<s:a href="%{#url}">Append Iterator Tag</s:a>
<li>
<s:url var="url" action="showMergeTagDemo" namespace="/tags/non-ui/mergeIteratorTag"/>
<s:a href="%{#url}" cssClass="dropdown-item">Merge Iterator Demo</s:a>
<s:url var="url" action="showMergeTagDemo"
namespace="/tags/non-ui/mergeIteratorTag"/>
<s:a href="%{#url}">Merge Iterator Demo</s:a>
<li>
<s:url var="url" action="showSubsetTagDemo" namespace="/tags/non-ui/subsetIteratorTag"/>
<s:a href="%{#url}" cssClass="dropdown-item">Subset Tag</s:a>
<li><s:url var="url" action="actionPrefixExampleUsingFreemarker" namespace="/tags/non-ui/actionPrefix"/>
<s:a href="%{#url}" cssClass="dropdown-item">Action Prefix Example (Freemarker)</s:a></li>
<li><s:url var="url" action="testIfTagJsp" namespace="/tags/non-ui/ifTag"/>
<s:a href="%{#url}" cssClass="dropdown-item">If Tag (JSP)</s:a></li>
<li><s:url var="url" action="testIfTagFreemarker" namespace="/tags/non-ui/ifTag"/>
<s:a href="%{#url}" cssClass="dropdown-item">If Tag (Freemarker)</s:a></li>
<s:url var="url" action="showSubsetTagDemo"
namespace="/tags/non-ui/subsetIteratorTag"/>
<s:a href="%{#url}">Subset Tag</s:a>
<li><s:url var="url" action="actionPrefixExampleUsingFreemarker"
namespace="/tags/non-ui/actionPrefix"/><s:a
href="%{#url}">Action Prefix Example (Freemarker)</s:a></li>
<li><s:url var="url" action="testIfTagJsp" namespace="/tags/non-ui/ifTag"/><s:a
href="%{#url}">If Tag (JSP)</s:a></li>
<li><s:url var="url" action="testIfTagFreemarker"
namespace="/tags/non-ui/ifTag"/><s:a
href="%{#url}">If Tag (Freemarker)</s:a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">UI Tags</a>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">UI Tags<b class="caret"></b></a>
<ul class="dropdown-menu" role="menu">
<li><s:url var="url" namespace="/tags/ui" action="example" method="input"/>
<s:a href="%{url}" cssClass="dropdown-item">UI Example</s:a></li>
<li><s:url var="url" namespace="/tags/ui" action="exampleVelocity" method="input"/>
<s:a href="%{url}" cssClass="dropdown-item">UI Example (Velocity)</s:a></li>
<li><s:url var="url" namespace="/tags/ui" action="lotsOfOptiontransferselect" method="input"/>
<s:a href="%{url}" cssClass="dropdown-item">Option Transfer Select UI Example</s:a></li>
<li><s:url var="url" namespace="/tags/ui" action="moreSelects" method="input"/>
<s:a href="%{url}" cssClass="dropdown-item">More Select Box UI Examples</s:a></li>
<li><s:url var="url" namespace="/tags/ui" action="example" method="input"/><s:a
href="%{url}">UI Example</s:a></li>
<li><s:url var="url" namespace="/tags/ui" action="exampleVelocity"
method="input"/><s:a href="%{url}">UI Example (Velocity)</s:a></li>
<li><s:url var="url" namespace="/tags/ui" action="lotsOfOptiontransferselect"
method="input"/><s:a
href="%{url}">Option Transfer Select UI Example</s:a></li>
<li><s:url var="url" namespace="/tags/ui" action="moreSelects" method="input"/><s:a
href="%{url}">More Select Box UI Examples</s:a></li>
<li>
<s:url var="url" namespace="/tags/ui" action="componentTagExample"/>
<s:a href="%{#url}" cssClass="dropdown-item">Component Tag Example</s:a></li>
<li><s:url var="url" namespace="/tags/ui" action="actionTagExample" method="input"/>
<s:a href="%{url}" cssClass="dropdown-item">Action Tag Example</s:a></li>
<li><s:url var="url" action="index" namespace="/html5"/>
<s:a href="%{#url}" cssClass="dropdown-item">Html 5 theme</s:a></li>
<s:a href="%{#url}">Component Tag Example</s:a></li>
<li><s:url var="url" namespace="/tags/ui" action="actionTagExample" method="input"/><s:a
href="%{url}">Action Tag Example</s:a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">
<i class="bi bi-file-earmark"></i> File</a>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="glyphicon glyphicon-file"></i> File
<b class="caret"></b></a>
<ul class="dropdown-menu" role="menu">
<li><s:a namespace="/filedownload" action="index" cssClass="dropdown-item">File Download</s:a></li>
<li><s:a namespace="/filedownload" action="index">File Download</s:a></li>
<li>
<s:url var="url" action="upload" namespace="/fileupload"/>
<s:a href="%{#url}" cssClass="dropdown-item">Single File Upload</s:a>
</li>
<li>
<s:url var="url" action="dynamicUpload" namespace="/fileupload"/>
<s:a href="%{#url}" cssClass="dropdown-item">Single File Upload - dynamic config</s:a>
<s:a href="%{#url}">Single File Upload</s:a>
</li>
<li>
<s:url var="url" action="multipleUploadUsingList" namespace="/fileupload"/>
<s:a href="%{#url}" cssClass="dropdown-item">Multiple File Upload (List)</s:a>
<s:a href="%{#url}">Multiple File Upload (List)</s:a>
</li>
<li>
<s:url var="url" action="multipleUploadUsingArray" namespace="/fileupload"/>
<s:a href="%{#url}" cssClass="dropdown-item">Multiple File Upload (Array)</s:a>
<s:a href="%{#url}">Multiple File Upload (Array)</s:a>
</li>
</ul>
</li>
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">Validation</a>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Validation<b class="caret"></b></a>
<ul class="dropdown-menu" role="menu">
<s:url var="quizBasic" namespace="/validation" action="quizBasic" method="input"/>
<s:url var="quizClient" namespace="/validation" action="quizClient" method="input"/>
@@ -216,64 +217,65 @@
<s:url var="storeMessageAcrossRequestExample" namespace="/validation" action="storeErrorsAcrossRequestExample"/>
<s:url var="beanValidationUrl" action="bean-validation" namespace="/bean-validation"/>
<s:url var="ajaxFormSubmitUrl" action="ajaxFormSubmit" namespace="/validation" method="input"/>
<li><s:a href="%{beanValidationUrl}" cssClass="dropdown-item">Bean Validation</s:a></li>
<li><s:a href="%{fieldValidatorUrl}" cssClass="dropdown-item">Field Validators</s:a></li>
<li><s:a href="%{clientSideValidationUrl}" cssClass="dropdown-item">Field Validators with client-side JavaScript</s:a></li>
<li><s:a href="%{nonFieldValidatorUrl}" cssClass="dropdown-item">Non Field Validator</s:a></li>
<li><s:a href="%{storeMessageAcrossRequestExample}" cssClass="dropdown-item">Store across request using MessageStoreInterceptor (Example)</s:a></li>
<li><s:a href="%{quizBasic}" cssClass="dropdown-item">Validation (basic)</s:a></li>
<li><s:a href="%{quizClient}" cssClass="dropdown-item">Validation (client)</s:a></li>
<li><s:a href="%{quizClientCss}" cssClass="dropdown-item">Validation (client using css_xhtml theme)</s:a></li>
<li><s:a href="%{visitorValidatorUrl}" cssClass="dropdown-item">Visitor Validator</s:a></li>
<li><s:a href="%{ajaxFormSubmitUrl}" cssClass="dropdown-item">AJAX Form Submit</s:a></li>
<li><s:a href="%{beanValidationUrl}">Bean Validation</s:a></li>
<li><s:a href="%{fieldValidatorUrl}">Field Validators</s:a></li>
<li><s:a href="%{clientSideValidationUrl}">Field Validators with client-side JavaScript</s:a></li>
<li><s:a href="%{nonFieldValidatorUrl}">Non Field Validator</s:a></li>
<li><s:a href="%{storeMessageAcrossRequestExample}">Store across request using MessageStoreInterceptor (Example)</s:a></li>
<li><s:a href="%{quizBasic}">Validation (basic)</s:a></li>
<li><s:a href="%{quizClient}">Validation (client)</s:a></li>
<li><s:a href="%{quizClientCss}">Validation (client using css_xhtml theme)</s:a></li>
<li><s:a href="%{visitorValidatorUrl}">Visitor Validator</s:a></li>
<li><s:a href="%{ajaxFormSubmitUrl}">AJAX Form Submit</s:a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">Examples</a>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Examples<b class="caret"></b></a>
<ul class="dropdown-menu" role="menu">
<li class="dropdown-submenu">
<li>
<s:url var="url" namespace="/hangman" action="hangmanNonAjax"/>
<s:a href="%{url}" cssClass="dropdown-item">Hangman</s:a>
<s:a href="%{url}">Hangman</s:a>
</li>
<li><s:a value="/person/index.html" cssClass="dropdown-item">Person Manager</s:a></li>
<li><s:a value="/skill/index.html" cssClass="dropdown-item">CRUD</s:a></li>
<li><s:a value="/wait/index" cssClass="dropdown-item">Execute &amp; Wait</s:a></li>
<li><s:a value="/token/index.html" cssClass="dropdown-item">Token</s:a></li>
<li><s:url var="url" namespace="/modelDriven" action="modelDriven"/><s:a cssClass="dropdown-item"
<li><s:a value="/person/index.html">Person Manager</s:a></li>
<li><s:a value="/skill/index.html">CRUD</s:a></li>
<li><s:a value="/wait/index">Execute &amp; Wait</s:a></li>
<li><s:a value="/token/index.html">Token</s:a></li>
<li><s:url var="url" namespace="/modelDriven" action="modelDriven"/><s:a
href="%{url}">Model Driven</s:a></li>
<li><s:a value="/async/index.html" cssClass="dropdown-item">Async</s:a></li>
<li><s:a value="/dispatcher/dispatch.action" cssClass="dropdown-item">Dispatcher result - dispatch</s:a></li>
<li><s:a value="/dispatcher/forward.action" cssClass="dropdown-item">Dispatcher result - forward</s:a></li>
<li><s:a value="/async/index.html">Async</s:a></li>
<li><s:a value="/dispatcher/dispatch.action">Dispatcher result - dispatch</s:a></li>
<li><s:a value="/dispatcher/forward.action">Dispatcher result - forward</s:a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">Integration</a>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Integration<b class="caret"></b></a>
<ul class="dropdown-menu" role="menu">
<li>
<s:url var="url" action="customFreemarkerManagerDemo" namespace="/freemarker"/>
<s:a href="%{#url}" cssClass="dropdown-item">Demo of usage of a Custom Freemarker Manager</s:a>
<s:a href="%{#url}">Demo of usage of a Custom Freemarker Manager</s:a>
</li>
<li>
<s:url var="url" action="standardTags" namespace="/freemarker"/>
<s:a href="%{#url}" cssClass="dropdown-item">Demo of Standard Struts Freemarker Tags</s:a>
<s:a href="%{#url}">Demo of Standard Struts Freemarker Tags</s:a>
</li>
<li><s:a value="/tiles/index.action" cssClass="dropdown-item">Tiles</s:a></li>
<li><s:a value="/tiles/index.action">Tiles</s:a></li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav ms-auto">
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">
<i class="bi bi-question-circle"></i> Help</a>
<ul class="nav navbar-nav pull-right">
<li class="dropdown last">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="glyphicon glyphicon-question-sign"></i> Help<b lass="caret"></b></a>
<ul class="dropdown-menu">
<s:url var="help" action="help" namespace="/" includeContext="false" />
<li><s:a value="%{help}" cssClass="dropdown-item">Help</s:a></li>
<li><a href="http://struts.apache.org/mail.html" class="dropdown-item"><i class="bi bi-share"></i> User Mailing
<li><s:a value="%{help}">Help</s:a></li>
<li><a href="http://struts.apache.org/mail.html"><i class="icon-share"></i> User Mailing
List</a></li>
<li><a href="http://struts.apache.org" class="dropdown-item"><i class="bi bi-share"></i> Struts2 Website</a>
<li><a href="http://struts.apache.org"><i class="icon-share"></i> Struts2 Website</a>
</li>
<li><a href="http://struts.apache.org/docs/home.html" class="dropdown-item"><i class="bi bi-share"></i>
<li><a href="http://struts.apache.org/docs/home.html"><i class="icon-share"></i>
Documentation</a></li>
</ul>
</li>
@@ -294,7 +296,7 @@
</div>
<div class="float-end">
<div class="pull-right">
<div>
<s:action var="dateAction" name="date" namespace="/" executeResult="true"/>
</div>
@@ -309,9 +311,11 @@
<!-- end search -->
</div>
<div class="float-start">
<div class="pull-left">
Copyright &copy; 2003-<s:property value="#dateAction.now.year + 1900"/>
<a href="https://www.apache.org">The Apache Software Foundation.</a>
<a href="http://www.apache.org">
The Apache Software Foundation.
</a>
</div>
</footer>
</body>
@@ -27,7 +27,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Dispatcher Result Example</h1>
</div>
@@ -31,7 +31,7 @@
<s:head/>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1><s:property value="#title"/></h1>
</div>
@@ -32,7 +32,7 @@
<title>Struts2 Showcase - CRUD Example - <s:property value="#title"/></title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1><s:property value="#title"/></h1>
</div>
@@ -24,7 +24,7 @@
<title>Struts2 Showcase - CRUD Example</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Available Employees</h1>
</div>
@@ -40,7 +40,7 @@
</div>
<div class="col-md-9">
<table class="table table-striped table-bordered table-hover table-sm">
<table class="table table-striped table-bordered table-hover table-condensed">
<tr>
<th>Id</th>
<th>First Name</th>
@@ -24,7 +24,7 @@
<title>Struts2 Showcase - CRUD Example</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Available Skills</h1>
</div>
@@ -40,7 +40,7 @@
</div>
<div class="col-md-9">
<table class="table table-striped table-bordered table-hover table-sm">
<table class="table table-striped table-bordered table-hover table-condensed">
<tr>
<th>Name</th><th>Description</th>
</tr>
@@ -25,7 +25,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>File Download Example</h1>
</div>
@@ -39,7 +39,7 @@
</div>
<s:url var="url" action="download"/>
<s:a href="%{url}" cssClass="btn btn-lg btn-info"><i class="bi bi-image"></i> Download image file.</s:a>
<s:a href="%{url}" cssClass="btn btn-large btn-info"><i class="icon-picture"></i> Download image file.</s:a>
</div>
<div class="col-md-6" style="text-align: center;">
<div class="alert alert-info">
@@ -47,7 +47,7 @@
</div>
<s:url var="url" action="download2"/>
<s:a href="%{url}" cssClass="btn btn-lg btn-info"><i class="bi bi-download"></i> Download ZIP file.</s:a>
<s:a href="%{url}" cssClass="btn btn-large btn-info"><i class="icon-download-alt"></i> Download ZIP file.</s:a>
</div>
</div>
</div>
@@ -1,109 +0,0 @@
<!DOCTYPE html>
<!--
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
-->
<%@ page
language="java"
contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html lang="en">
<head>
<title>Struts2 Showcase - Dynamic File Upload Success</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<h1>File Upload Successful</h1>
<p class="lead">Your file was validated and uploaded successfully</p>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="alert alert-success">
<strong>Success!</strong> Your file passed all validation checks.
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title">Upload Details</h3>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-3">Upload Type:</dt>
<dd class="col-sm-9"><s:property value="uploadType == 'image' ? 'Image' : 'Document'"/></dd>
<dt class="col-sm-3">Content Type:</dt>
<dd class="col-sm-9"><code><s:property value="contentType"/></code></dd>
<dt class="col-sm-3">File Name:</dt>
<dd class="col-sm-9"><s:property value="fileName"/></dd>
<dt class="col-sm-3">Original Name:</dt>
<dd class="col-sm-9"><s:property value="originalName"/></dd>
<dt class="col-sm-3">File Size:</dt>
<dd class="col-sm-9"><s:property value="uploadSize"/> bytes</dd>
<dt class="col-sm-3">Input Name:</dt>
<dd class="col-sm-9"><s:property value="inputName"/></dd>
<dt class="col-sm-3">File Object:</dt>
<dd class="col-sm-9"><code><s:property value="uploadedFile"/></code></dd>
</dl>
</div>
</div>
<div class="card border-info">
<div class="card-header text-bg-info">
<h3 class="card-title">Validation Rules Applied</h3>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-3">Allowed MIME Types:</dt>
<dd class="col-sm-9"><code><s:property value="uploadConfig.allowedMimeTypes"/></code></dd>
<dt class="col-sm-3">Allowed Extensions:</dt>
<dd class="col-sm-9"><code><s:property value="uploadConfig.allowedExtensions"/></code></dd>
<dt class="col-sm-3">Maximum Size:</dt>
<dd class="col-sm-9"><s:property value="uploadConfig.maxFileSizeFormatted"/></dd>
</dl>
<p class="text-muted">
<small>
These validation rules were determined dynamically at runtime
using <code>WithLazyParams</code> and evaluated from the ValueStack.
</small>
</p>
</div>
</div>
<div class="btn-group">
<s:a action="dynamicUpload" cssClass="btn btn-primary">
<i class="bi bi-upload"></i> Upload Another File
</s:a>
</div>
</div>
</div>
</div>
</body>
</html>
@@ -1,101 +0,0 @@
<!DOCTYPE html>
<!--
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
-->
<%@ taglib prefix="s" uri="/struts-tags" %>
<html lang="en">
<head>
<title>Struts2 Showcase - Dynamic File Upload Validation</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<h1>Dynamic File Upload Validation</h1>
<p class="lead">Demonstrates WithLazyParams for runtime validation rules</p>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="alert alert-info">
<h4>About This Example</h4>
<p>
This example demonstrates how to use <code>WithLazyParams</code> to configure
file upload validation rules dynamically at runtime. The validation parameters
(<code>allowedTypes</code>, <code>allowedExtensions</code>, <code>maximumSize</code>)
are evaluated from the ValueStack for each request, allowing different rules
based on action state, user permissions, or other runtime conditions.
</p>
</div>
<div class="alert alert-success">
<h4>Current Configuration</h4>
<ul>
<li><strong>Upload Type:</strong> <s:property
value="uploadType == 'image' ? 'Image Upload' : 'Document Upload'"/></li>
<li><strong>Allowed Types:</strong> <code><s:property value="uploadConfig.allowedMimeTypes"/></code>
</li>
<li><strong>Allowed Extensions:</strong> <code><s:property
value="uploadConfig.allowedExtensions"/></code></li>
<li><strong>Maximum Size:</strong> <s:property value="uploadConfig.maxFileSizeFormatted"/></li>
<li><strong>Description:</strong> <s:property value="uploadConfig.description"/></li>
</ul>
</div>
</div>
</div>
<s:actionerror cssClass="alert alert-danger"/>
<s:fielderror cssClass="alert alert-warning"/>
<div class="row">
<div class="col-md-12">
<s:form action="doDynamicUpload" method="POST" enctype="multipart/form-data" cssClass="form-vertical">
<s:radio name="uploadType" label="Upload type"
list="#{'document':'Documents (PDF, Word) - up to 5MB', 'image':'Images (JPEG, PNG) - up to 2MB'}"/>
<s:file name="upload" label="Select File" cssClass="form-control"/>
<s:submit value="Upload File" cssClass="btn btn-primary"/>
<s:submit value="Refresh Rules" action="dynamicUpload" cssClass="btn btn-secondary"/>
</s:form>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="bg-light border rounded p-3">
<h4>How It Works</h4>
<p>In <code>struts.xml</code>, the interceptor parameters use expressions:</p>
<pre>&lt;interceptor-ref name="actionFileUpload"&gt;
&lt;param name="allowedTypes"&gt;<strong>${uploadConfig.allowedMimeTypes}</strong>&lt;/param&gt;
&lt;param name="allowedExtensions"&gt;<strong>${uploadConfig.allowedExtensions}</strong>&lt;/param&gt;
&lt;param name="maximumSize"&gt;<strong>${uploadConfig.maxFileSize}</strong>&lt;/param&gt;
&lt;/interceptor-ref&gt;</pre>
<p>
These expressions are evaluated at runtime against the ValueStack,
allowing the action to control validation rules dynamically in its
<code>prepare()</code> method.
</p>
</div>
</div>
</div>
</div>
</body>
</html>
@@ -29,7 +29,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Fileupload sample - Multiple fileupload</h1>
</div>
@@ -29,7 +29,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Fileupload sample - Multiple fileupload using Array</h1>
</div>
@@ -29,7 +29,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Fileupload sample - Multiple fileupload using List</h1>
</div>
@@ -29,7 +29,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Fileupload sample</h1>
</div>
@@ -25,15 +25,15 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Fileupload sample</h1>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<s:actionerror cssClass="alert alert-danger"/>
<s:fielderror cssClass="alert alert-danger"/>
<s:actionerror cssClass="alert alert-error"/>
<s:fielderror cssClass="alert alert-error"/>
<s:form action="doUpload" method="POST" enctype="multipart/form-data">
<s:file name="upload" label="File"/>
<s:textfield name="caption" label="Caption"/>
@@ -24,7 +24,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Custom Freemarker Manager Usage</h1>
</div>
@@ -24,7 +24,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Standard Struts Freemarker Tags</h1>
</div>
@@ -23,7 +23,7 @@
<title>Struts2 Showcase - Hangman</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Hangman</h1>
</div>
@@ -25,7 +25,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Getting support</h1>
</div>
@@ -39,13 +39,13 @@
<div class="alert alert-info">
Use this mailing list if you encounter problems while developing and using with Struts.
</div>
<a href="http://struts.apache.org/mail.html" class="btn btn-lg btn-info"><i class="bi bi-share"></i> User List</a>
<a href="http://struts.apache.org/mail.html" class="btn btn-large btn-info"><i class="glyphicon glyphicon-share"></i> User List</a>
</div>
<div class="col-md-4" style="text-align: center;">
<div class="alert alert-info">
The Struts 2 website.
</div>
<a href="http://struts.apache.org" class="btn btn-lg btn-info"><i class="bi bi-share"></i> Struts 2</a>
<a href="http://struts.apache.org" class="btn btn-large btn-info"><i class="glyphicon glyphicon-share"></i> Struts 2</a>
</div>
</div>
</body>
@@ -1,282 +0,0 @@
<!--
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
-->
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html>
<s:compress>
<html lang="en">
<head>
<link rel="stylesheet" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
<s:url var="mainCss" value="/styles/main.css" encode="false" includeParams="none"/>
<s:link theme="html5" href="%{mainCss}" />
<s:head theme="html5"/>
<title>Struts2 Showcase - Html5 theme</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="p-5 mb-4 bg-light rounded-3">
<h1>Html 5 tags demo</h1>
<p>All the tags on this page are from <i>html5</i> theme. <s:a theme="html5" action="showcase" namespace="/">Back</s:a> to main Showcase App page</p>
</div>
</div>
</div>
<!-- Section 1: Link Components -->
<div class="row">
<div class="col-md-12">
<div class="border-bottom pb-2 mb-3">
<h2>Link Components</h2>
</div>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:a/&gt;</pre>
</div>
<div class="col-md-10">
<s:a theme="html5" action="index">index</s:a>
</div>
</div>
<!-- Section 2: Error & Message Components -->
<div class="row">
<div class="col-md-12">
<div class="border-bottom pb-2 mb-3">
<h2>Error &amp; Message Components</h2>
</div>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:actionerror/&gt;</pre>
</div>
<div class="col-md-10">
<s:actionerror theme="html5"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:actionmessage/&gt;</pre>
</div>
<div class="col-md-10">
<s:actionmessage theme="html5"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:fielderror/&gt;</pre>
</div>
<div class="col-md-10">
<s:fielderror theme="html5"/>
</div>
</div>
<!-- Section 3: Form Components -->
<div class="row">
<div class="col-md-12">
<div class="border-bottom pb-2 mb-3">
<h2>Form Components</h2>
</div>
</div>
</div>
<s:form theme="html5" action="index" method="post">
<div class="row">
<div class="col-md-2">
<pre>&lt;s:form/&gt;</pre>
</div>
<div class="col-md-10">
<p>Form wrapper (wraps all components below)</p>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:textfield/&gt;</pre>
</div>
<div class="col-md-10">
<s:textfield theme="html5" label="Name" name="name" tooltip="Enter your name here"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:password/&gt;</pre>
</div>
<div class="col-md-10">
<s:password theme="html5" label="Password" name="password"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:textarea/&gt;</pre>
</div>
<div class="col-md-10">
<s:textarea theme="html5" label="Comments" name="comments" cols="40" rows="3"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:hidden/&gt;</pre>
</div>
<div class="col-md-10">
<s:hidden theme="html5" name="hiddenValue" value="secret"/>
<p><small>Hidden field with value="secret" (not visible)</small></p>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:checkbox/&gt;</pre>
</div>
<div class="col-md-10">
<s:checkbox theme="html5" label="Accept Terms" name="terms"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:radio/&gt;</pre>
</div>
<div class="col-md-10">
<s:radio theme="html5" label="Gender" list="{'Male', 'Female', 'Other'}" name="gender"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:select/&gt;</pre>
</div>
<div class="col-md-10">
<s:select theme="html5" label="Country" list="{'USA', 'UK', 'Canada', 'Australia'}" name="country" emptyOption="true" headerKey="" headerValue="-- Please Select --"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:checkboxlist/&gt;</pre>
</div>
<div class="col-md-10">
<s:checkboxlist theme="html5" label="Interests" list="{'Sports', 'Music', 'Reading', 'Travel'}" name="interests"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:file/&gt;</pre>
</div>
<div class="col-md-10">
<s:file theme="html5" label="Upload File" name="upload"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:token/&gt;</pre>
</div>
<div class="col-md-10">
<s:token theme="html5"/>
<p><small>CSRF token (hidden, check page source)</small></p>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:submit/&gt;</pre>
</div>
<div class="col-md-10">
<s:submit theme="html5" value="Submit" cssClass="btn btn-primary"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:reset/&gt;</pre>
</div>
<div class="col-md-10">
<s:reset theme="html5" value="Reset" cssClass="btn btn-danger"/>
</div>
</div>
</s:form>
<!-- Section 4: Advanced Selection Components -->
<div class="row">
<div class="col-md-12">
<div class="border-bottom pb-2 mb-3">
<h2>Advanced Selection Components</h2>
</div>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:combobox/&gt;</pre>
</div>
<div class="col-md-10">
<s:combobox theme="html5" label="Favourite City" name="city" list="{'New York', 'London', 'Tokyo', 'Paris'}"/>
</div>
</div>
<!-- Section 5: Utility & Display Components -->
<div class="row">
<div class="col-md-12">
<div class="border-bottom pb-2 mb-3">
<h2>Utility &amp; Display Components</h2>
</div>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:label/&gt;</pre>
</div>
<div class="col-md-10">
<s:label theme="html5" label="Display Label" name="displayValue" value="Read-only Value"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:script/&gt;</pre>
</div>
<div class="col-md-10">
<s:script theme="html5">
console.log('HTML5 theme script tag example');
</s:script>
<p><small>Script tag (check browser console for output)</small></p>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:debug/&gt;</pre>
</div>
<div class="col-md-10">
<s:debug theme="html5"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:head/&gt;</pre>
</div>
<div class="col-md-10">
<p><small>Already used in page &lt;head&gt; section (line 30)</small></p>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:link/&gt;</pre>
</div>
<div class="col-md-10">
<p><small>Already used in page &lt;head&gt; section for CSS (lines 27, 29)</small></p>
</div>
</div>
</div>
</body>
</html>
</s:compress>
@@ -27,7 +27,7 @@
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Model Driven Example</h1>
</div>
@@ -34,7 +34,7 @@
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Model Driven Example - Result</h1>
</div>
@@ -24,7 +24,7 @@
<title>Struts2 Showcase - Person Manager Example</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Person Manager Example</h1>
</div>
@@ -43,7 +43,7 @@
<div class="col-md-9">
<s:form action="edit-person" theme="simple" validate="false">
<table class="table table-striped table-bordered table-hover table-sm">
<table class="table table-striped table-bordered table-hover table-condensed">
<tr>
<th>ID</th>
<th>First Name</th>
@@ -23,7 +23,7 @@
<title>Struts2 Showcase - Person Manager Example - All People</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>All People</h1>
</div>
@@ -43,7 +43,7 @@
<p>There are ${peopleCount} people...</p>
<table class="table table-striped table-bordered table-hover table-sm">
<table class="table table-striped table-bordered table-hover table-condensed">
<tr>
<th>ID</th>
<th>Name</th>
@@ -23,7 +23,7 @@
<title>Struts2 Showcase - Person Manager Example - New Person</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>New Person</h1>
</div>
@@ -40,21 +40,25 @@
</ul>
</div>
<div class="col-md-9">
<@s.actionerror cssClass="alert alert-danger"/>
<@s.actionerror cssClass="alert alert-error"/>
<@s.actionmessage cssClass="alert alert-info"/>
<@s.fielderror cssClass="alert alert-danger"/>
<@s.fielderror cssClass="alert alert-error"/>
<@s.form action="new-person" theme="simple">
<@s.form action="new-person" theme="simple" cssClass="form-horizontal">
<legend>Create a new Person</legend>
<div class="mb-3">
<label class="form-label" for="name">First Name<span class="required">*</span></label>
<@s.textfield id="name" name="person.name" placeholder="First Name" cssClass="form-control"/>
<div class="control-group">
<label class="control-label" for="name">First Name<span class="required">*</span></label>
<div class="controls">
<@s.textfield id="name" name="person.name" placeholder="First Name"/>
</div>
</div>
<div class="mb-3">
<label class="form-label" for="lastName">Last Name<span class="required">*</span></label>
<@s.textfield id="lastName" name="person.lastName" placeholder="Last Name" cssClass="form-control"/>
<div class="control-group">
<label class="control-label" for="lastName">Last Name<span class="required">*</span></label>
<div class="controls">
<@s.textfield id="lastName" name="person.lastName" placeholder="Last Name"/>
</div>
</div>
<div class="mb-3">
<div class="form-actions">
<@s.submit value="Create person" cssClass="btn btn-primary"/>
</div>
</@s.form>
@@ -36,10 +36,10 @@
<div class="row">
<div class="col-md-12">
<div class="p-5 mb-4 bg-light rounded-3">
<div class="hero-unit">
<h1>Welcome!</h1>
<p>The Struts Showcase demonstrates a variety of use cases and tag usages. Essentially, the application exercises various framework features in isolation. The Showcase is not meant as a "best practices" example.</p>
<p>For more "by example" solutions, see the <a href="https://github.com/apache/struts-examples" class="btn btn-primary btn-lg">Struts Examples &raquo;</a> pages.</p>
<p>For more "by example" solutions, see the <a href="https://github.com/apache/struts-examples" class="btn btn-primary btn-large">Struts Examples &raquo;</a> pages.</p>
</div>
</div>
@@ -27,5 +27,4 @@
<mapping path="/images/*" exclude="true"/>
<mapping path="/static/*" exclude="true"/>
<mapping path="/nodecorate/*" exclude="true"/>
<mapping path="/html5/*" exclude="true"/>
</sitemesh>
@@ -23,7 +23,7 @@
<title>Struts2 Showcase - Non UI Tags - Action Prefix (Freemarker)</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Non Ui Tag - Action Prefix (Freemarker)</h1>
</div>
@@ -35,7 +35,7 @@
<p>The text you've entered is ${text!''}<p/>
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back</@s.a>
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back</@s.a>
</div>
</div>
</div>
@@ -23,7 +23,7 @@
<title>Struts2 Showcase - Non UI Tags - Action Prefix (Freemarker)</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Non Ui Tag - Action Prefix (Freemarker)</h1>
</div>
@@ -23,7 +23,7 @@
<title>Struts2 Showcase - Non UI Tags - Action Prefix (Freemarker)</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Non Ui Tag - Action Prefix (Freemarker)</h1>
</div>
@@ -35,7 +35,7 @@
<p>The text you've enter is ${text!''}<p/>
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back</@s.a>
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back</@s.a>
</div>
</div>
</div>
@@ -23,7 +23,7 @@
<title>Struts2 Showcase - Non UI Tags - Action Prefix (Freemarker)</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Non Ui Tag - Action Prefix (Freemarker)</h1>
</div>
@@ -35,7 +35,7 @@
<p>The text you've enter is %{text}<p/>
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back</@s.a>
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back</@s.a>
</div>
</div>
</div>
@@ -23,7 +23,7 @@
<title>Struts2 Showcase - Non UI Tags - Action Prefix (Freemarker)</title>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Non Ui Tag - Action Prefix (Freemarker)</h1>
</div>
@@ -38,7 +38,7 @@
The text you've enter is ${text!''}<p/>
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back</@s.a>
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back</@s.a>
</div>
</div>
</div>
@@ -25,7 +25,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Non-Ui Tag - Action Tag</h1>
</div>
@@ -33,13 +33,13 @@
<div class="row">
<div class="col-md-12">
<div class="bg-light border rounded p-3">
<div class="well">
<h2> This is Not - Included by the Action Tag</h2>
</div>
<!-- lets include the first page many times -->
<div class="bg-light border rounded p-3">
<div class="well">
<s:action name="includePage" namespace="/tags/non-ui/actionTag" executeResult="true" />
<s:action name="includePage" namespace="/tags/non-ui/actionTag" executeResult="true" />
<s:action name="includePage" namespace="/tags/non-ui/actionTag" executeResult="true" />
@@ -47,7 +47,7 @@
<!-- lets include the second page many times -->
<div class="bg-light border rounded p-3">
<div class="well">
<s:action name="includePage2" namespace="/tags/non-ui/actionTag" executeResult="true" />
<s:action name="includePage2" namespace="/tags/non-ui/actionTag" executeResult="true" />
<s:action name="includePage2" namespace="/tags/non-ui/actionTag" executeResult="true" />
@@ -55,7 +55,7 @@
<!-- lets include the third page many time -->
<div class="bg-light border rounded p-3">
<div class="well">
<s:action name="includePage3" namespace="/tags/non-ui/actionTag" executeResult="true" />
<s:action name="includePage3" namespace="/tags/non-ui/actionTag" executeResult="true" />
<s:action name="includePage3" namespace="/tags/non-ui/actionTag" executeResult="true" />
@@ -25,7 +25,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Non UI Tags Example - Date</h1>
</div>
@@ -35,7 +35,7 @@
<s:action var="myDate" name="date" namespace="/" executeResult="false" />
<table class="table table-striped table-bordered table-hover table-sm">
<table class="table table-striped table-bordered table-hover table-condensed">
<tr>
<th>Name</th>
<th>Format</th>
@@ -25,7 +25,7 @@
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Debug Tag Usage</h1>
</div>
@@ -24,7 +24,7 @@
<s:head/>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Non Ui Tag - Test If Tag (Freemarker)</h1>
</div>
@@ -25,7 +25,7 @@
<s:head/>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Non Ui Tag - Test If Tag</h1>
</div>
@@ -25,7 +25,7 @@
<s:head/>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Non Ui Tag - AppendIterator Tag Demo</h1>
</div>
@@ -46,7 +46,7 @@
</s:iterator>
<s:url var="url" action="showAppendTagDemo" namespace="/tags/non-ui/appendIteratorTag" />
<s:a href="%{#url}" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back To Input</s:a>
<s:a href="%{#url}" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
</div>
</div>
</div>
@@ -25,7 +25,7 @@
<s:head/>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Non Ui Tag - Iterator Generator Tag Demo</h1>
</div>
@@ -41,7 +41,7 @@
<s:url var="url" action="showGeneratorTagDemo" namespace="/tags/non-ui/iteratorGeneratorTag" />
<s:a href="%{#url}" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back To Input</s:a>
<s:a href="%{#url}" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
</div>
</div>
</div>
@@ -25,7 +25,7 @@
<s:head/>
</head>
<body>
<div class="border-bottom pb-2 mb-3">
<div class="page-header">
<h1>Non Ui Tag - MergeIterator Tag</h1>
</div>
@@ -45,7 +45,7 @@
</s:iterator>
<s:url var="url" action="showMergeTagDemo" namespace="/tags/non-ui/mergeIteratorTag" />
<s:a href="%{#url}" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back To Input</s:a>
<s:a href="%{#url}" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
</div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More