Skip to content

Run boot war on standalone Apache Tomcat#3825

Merged
fhanik merged 2 commits intocloudfoundry:developfrom
fhanik:pr/run-on-tomcat
Apr 10, 2026
Merged

Run boot war on standalone Apache Tomcat#3825
fhanik merged 2 commits intocloudfoundry:developfrom
fhanik:pr/run-on-tomcat

Conversation

@fhanik
Copy link
Copy Markdown
Contributor

@fhanik fhanik commented Apr 10, 2026

#3819

Summary

Tomcat filter / WAR startup

  • Cherry-picked 8bac4bbd so zone path filters work on a classic servlet WAR on external Tomcat: Filter beans use the existing BEAN_NAME for DelegatingFilterProxy, and FilterRegistrationBean beans use new REGISTRATION_BEAN_NAME constants. MockMvc/docs tests that inject the registration beans were updated to use the new qualifiers.

Standalone Tomcat integration tests (CI + local)

  • Gradle: writeTomcatDistributionVersion / printTomcatDistributionVersion resolve the Apache Tomcat distro version from the same tomcat-embed-core version as the Spring Boot BOM and write build/tomcat-distribution.version.
  • cloudfoundry-identity-uaa integrationTest: If UAA_INTEGRATION_SERVER=external or -PuaaIntegrationServer=external, skips the embedded java -jar start/stop (for use when Tomcat is already running). Uses rootProject.findProperty so -P works from the root build.
  • Scripts under scripts/tomcat/:
    • integration_tests_tomcat.sh — downloads/extracts Tomcat, writes setenv.sh (JVM flags aligned with boot integration tests; no set -u so Tomcat’s setclasspath.sh works), deploys uaa.war, adds conf/Catalina/localhost/uaa.xml with useRelativeRedirects="false" so redirect Location headers are absolute (fixes HttpClient 5 “Target host is not specified” after form login), waits on HTTP readiness, runs integrationTest with external server, stops Tomcat. Includes early port cleanup and stale pid removal so a failed run doesn’t leave 8080 occupied or confuse the next run.
    • kill_tomcat.shshutdown.sh when CATALINA_HOME is set, then lsof + port kill.
  • GitHub Actions: New job tomcat-integration-test on cfidentity/uaa-postgresql-15, running scripts/tomcat/integration_tests_tomcat.sh with postgresql,default.

Test logging

  • Subproject test / integrationTest tasks: per-test PASSED/SKIPPED/FAILED logging is on by default; disable with UAA_VERBOSE_TESTS / -PverboseTests in false / 0 / off / no. Documented on the integration scripts.

Layout / hygiene

  • Tomcat scripts live under scripts/tomcat/ (not the repo root scripts/); CI and paths were updated accordingly.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Enables UAA to run as a classic servlet WAR on an externally managed Apache Tomcat by correcting filter bean naming/registration, and adds CI + local tooling to run integration tests against a standalone Tomcat distribution aligned with the Spring Boot BOM.

Changes:

  • Split zone path/session filters into distinct Filter beans (for DelegatingFilterProxy in external Tomcat WAR) and separately named FilterRegistrationBeans (for Spring Boot registration).
  • Add standalone Tomcat integration-test scripts and a new GitHub Actions job to run them.
  • Improve test task logging defaults and add helper Gradle tasks to resolve/write the Tomcat distribution version.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenExchangeGrantEndpointDocs.java Update qualifiers to reference the new registration-bean names.
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/JwtBearerGrantEndpointDocs.java Update qualifiers to reference the new registration-bean names.
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/EndpointDocs.java Update qualifiers to reference the new registration-bean names.
uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java Update qualifiers to reference the new registration-bean names.
uaa/src/test/java/org/cloudfoundry/identity/uaa/DefaultTestContext.java Update MockMvc wiring to inject the registration beans using the new qualifiers.
uaa/build.gradle Allow integration tests to run against an externally managed server (skip embedded java -jar lifecycle).
server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZonePathContextRewritingFilterConfiguration.java Provide both Filter and FilterRegistrationBean beans with separate names to support external Tomcat WAR bootstrap.
server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZonePathContextRewritingFilter.java Add a constant for the registration-bean name.
server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneContextPathSessionFilter.java Add a constant for the registration-bean name.
scripts/tomcat/kill_tomcat.sh Add helper to stop Tomcat and kill processes bound to the port.
scripts/tomcat/integration_tests_tomcat.sh Add end-to-end standalone Tomcat download/deploy/run script for integration tests.
scripts/integration_tests.sh Document verbose per-test logging toggle.
build.gradle Default per-test logging to include passed/skipped/failed; add tasks to write/print the Tomcat distribution version.
.github/workflows/integration-tests.yml Add a new standalone Tomcat integration-test job and upload artifacts on failure.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +19 to 33
@Bean(ZonePathContextRewritingFilter.BEAN_NAME)
ZonePathContextRewritingFilter zonePathContextRewritingFilter(
@Value("${zones.paths.enabled:false}") boolean zonePathsEnabled) {
return new ZonePathContextRewritingFilter(zonePathsEnabled);
}

/**
* Zone path rewriting runs as a servlet filter (enabled) so it executes before Spring Security
* selects a filter chain. That way the request path is rewritten to context path + servlet path
* (e.g. /z/myzone + /Codes) before security matchers run, so patterns like /Codes/** match correctly.
*/
@Bean(ZonePathContextRewritingFilter.BEAN_NAME)
FilterRegistrationBean<ZonePathContextRewritingFilter> zonePathContextRewritingFilter(
@Value("${zones.paths.enabled:false}") boolean zonePathsEnabled) {
ZonePathContextRewritingFilter filter = new ZonePathContextRewritingFilter(zonePathsEnabled);
@Bean(ZonePathContextRewritingFilter.REGISTRATION_BEAN_NAME)
FilterRegistrationBean<ZonePathContextRewritingFilter> zonePathContextRewritingFilterRegistration(
@Qualifier(ZonePathContextRewritingFilter.BEAN_NAME) ZonePathContextRewritingFilter filter) {
FilterRegistrationBean<ZonePathContextRewritingFilter> bean = new FilterRegistrationBean<>(filter);
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

ZonePathContextRewritingFilter is now defined as a Filter bean and also registered via a FilterRegistrationBean. In a Spring Boot embedded container, Filter-typed beans are eligible for automatic servlet filter registration, which can result in this filter being registered twice (once implicitly, once via the explicit registration bean). For this specific filter, a double invocation can corrupt ZONE_ORIGINAL_CONTEXT_PATH and cookie path rewriting (the second pass sees a different contextPath). Consider restructuring so only one servlet filter registration exists in Boot mode (e.g., rely solely on FilterRegistrationBean by not exposing a Filter-typed bean, or drop the FilterRegistrationBean and use @Order on the filter bean).

Copilot uses AI. Check for mistakes.
Comment on lines +44 to 58
@Bean(ZoneContextPathSessionFilter.BEAN_NAME)
ZoneContextPathSessionFilter zoneContextPathSessionFilter(TimeService timeService) {
return new ZoneContextPathSessionFilter(timeService);
}

/**
* Registers ZoneContextPathSessionFilter to run AFTER SessionRepositoryFilter (HIGHEST_PRECEDENCE + 50)
* so that it wraps the Spring Session-backed request. This ensures request.getSession() first goes
* through ZoneContextPathSessionFilter (returning a zone-scoped subsession view) which delegates
* to SessionRepositoryFilter's session (the actual Spring Session-backed session).
*/
@Bean(ZoneContextPathSessionFilter.BEAN_NAME)
FilterRegistrationBean<ZoneContextPathSessionFilter> zoneContextPathSessionFilter(TimeService timeService) {
ZoneContextPathSessionFilter filter = new ZoneContextPathSessionFilter(timeService);
@Bean(ZoneContextPathSessionFilter.REGISTRATION_BEAN_NAME)
FilterRegistrationBean<ZoneContextPathSessionFilter> zoneContextPathSessionFilterRegistration(
@Qualifier(ZoneContextPathSessionFilter.BEAN_NAME) ZoneContextPathSessionFilter filter) {
FilterRegistrationBean<ZoneContextPathSessionFilter> bean = new FilterRegistrationBean<>(filter);
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

Same concern as the zone path rewriting filter: ZoneContextPathSessionFilter is now exposed as a Filter bean and also registered via a FilterRegistrationBean. If Spring Boot auto-registers Filter beans, this can lead to two registrations with different filter names/orders, and the session-wrapping logic could be applied twice or in the wrong position relative to SessionRepositoryFilter. Recommend ensuring there is exactly one servlet registration for this filter in embedded Boot mode.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@strehle strehle left a comment

Choose a reason for hiding this comment

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

Please check the comments from copilot

@fhanik
Copy link
Copy Markdown
Contributor Author

fhanik commented Apr 10, 2026

Double checked filter registration, no duplicate calls are possible.

@fhanik fhanik merged commit 54cc922 into cloudfoundry:develop Apr 10, 2026
39 of 41 checks passed
@github-project-automation github-project-automation bot moved this from Pending Merge | Prioritized to Done in Foundational Infrastructure Working Group Apr 10, 2026
@fhanik fhanik deleted the pr/run-on-tomcat branch April 10, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

UAA 78.10.0 deploying to external tomcat as war throwing BeanNotOfRequiredTypeException Exception

3 participants