From e2d7c540550e167c93227af8e43de2eed6102f01 Mon Sep 17 00:00:00 2001 From: Kiril Keranov <114745615+kiril-keranov@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:05:36 +0200 Subject: [PATCH 1/5] Refactor classpath handling for client certificate mapper --- .../frameworks/client_certificate_mapper.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/java/frameworks/client_certificate_mapper.go b/src/java/frameworks/client_certificate_mapper.go index 879e2819a..f0fae5ed4 100644 --- a/src/java/frameworks/client_certificate_mapper.go +++ b/src/java/frameworks/client_certificate_mapper.go @@ -69,17 +69,17 @@ func (c *ClientCertificateMapperFramework) Finalize() error { return nil } - // Add to classpath via CLASSPATH environment variable - classpath := os.Getenv("CLASSPATH") - if classpath != "" { - classpath += ":" - } - classpath += matches[0] + depsIdx := c.context.Stager.DepsIdx() + runtimePath := fmt.Sprintf("$DEPS_DIR/%s/client_certificate_mapper/%s", depsIdx, filepath.Base(matches[0])) + + profileScript := fmt.Sprintf("export CLASSPATH=\"%s${CLASSPATH:+:$CLASSPATH}\"\n", runtimePath) - if err := c.context.Stager.WriteEnvFile("CLASSPATH", classpath); err != nil { - return fmt.Errorf("failed to set CLASSPATH for Client Certificate Mapper: %w", err) + if err := c.context.Stager.WriteProfileD("client_certificate_mapper.sh", profileScript); err != nil { + return fmt.Errorf("failed to write client_certificate_mapper.sh profile.d script: %w", err) } - + + c.context.Log.Debug("Client Certificate Mapper JAR will be added to classpath at runtime: %s", runtimePath) + return nil } From f4b7d9821ec92ed832fe7e774bc40c71c55e6299 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Tue, 10 Mar 2026 13:14:06 +0200 Subject: [PATCH 2/5] Fix classpath handling for dependency frameworks --- src/java/frameworks/java_cf_env.go | 17 ++++++++--------- src/java/frameworks/maria_db_jdbc.go | 7 +++++-- src/java/frameworks/postgresql_jdbc.go | 16 +++++++--------- .../frameworks/spring_auto_reconfiguration.go | 15 +++++++-------- 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/java/frameworks/java_cf_env.go b/src/java/frameworks/java_cf_env.go index 3924342b9..f1a0294ef 100644 --- a/src/java/frameworks/java_cf_env.go +++ b/src/java/frameworks/java_cf_env.go @@ -1,8 +1,8 @@ package frameworks import ( - "github.com/cloudfoundry/java-buildpack/src/java/common" "fmt" + "github.com/cloudfoundry/java-buildpack/src/java/common" "os" "path/filepath" "strings" @@ -81,17 +81,16 @@ func (j *JavaCfEnvFramework) Finalize() error { return nil } - // Add to classpath via CLASSPATH environment variable - classpath := os.Getenv("CLASSPATH") - if classpath != "" { - classpath += ":" - } - classpath += matches[0] + depsIdx := j.context.Stager.DepsIdx() + runtimePath := fmt.Sprintf("$DEPS_DIR/%s/java_cf_env/%s", depsIdx, filepath.Base(matches[0])) - if err := j.context.Stager.WriteEnvFile("CLASSPATH", classpath); err != nil { - return fmt.Errorf("failed to set CLASSPATH for Java CF Env: %w", err) + profileScript := fmt.Sprintf("export CLASSPATH=\"%s${CLASSPATH:+:$CLASSPATH}\"\n", runtimePath) + if err := j.context.Stager.WriteProfileD("java_cf_env.sh", profileScript); err != nil { + return fmt.Errorf("failed to write java_cf_env.sh profile.d script: %w", err) } + j.context.Log.Debug("Java CF Env JAR will be added to classpath at runtime: %s", runtimePath) + return nil } diff --git a/src/java/frameworks/maria_db_jdbc.go b/src/java/frameworks/maria_db_jdbc.go index 3f8010e74..da85c65f1 100644 --- a/src/java/frameworks/maria_db_jdbc.go +++ b/src/java/frameworks/maria_db_jdbc.go @@ -88,8 +88,11 @@ func (f *MariaDBJDBCFramework) Finalize() error { f.context.Log.BeginStep("Configuring MariaDB JDBC driver") - // Add to CLASSPATH environment variable - if err := f.context.Stager.WriteEnvFile("CLASSPATH", f.jarPath); err != nil { + depsIdx := f.context.Stager.DepsIdx() + runtimePath := fmt.Sprintf("$DEPS_DIR/%s/mariadb_jdbc/%s", depsIdx, filepath.Base(f.jarPath)) + + profileScript := fmt.Sprintf("export CLASSPATH=\"%s${CLASSPATH:+:$CLASSPATH}\"\n", runtimePath) + if err := f.context.Stager.WriteProfileD("mariadb_jdbc.sh", profileScript); err != nil { f.context.Log.Warning("Failed to add MariaDB JDBC to CLASSPATH: %s", err) return nil // Non-blocking } diff --git a/src/java/frameworks/postgresql_jdbc.go b/src/java/frameworks/postgresql_jdbc.go index f8f99605b..8d9310a4a 100644 --- a/src/java/frameworks/postgresql_jdbc.go +++ b/src/java/frameworks/postgresql_jdbc.go @@ -3,7 +3,6 @@ package frameworks import ( "fmt" "github.com/cloudfoundry/java-buildpack/src/java/common" - "os" "path/filepath" "strings" @@ -73,17 +72,16 @@ func (p *PostgresqlJdbcFramework) Finalize() error { return nil } - // Add to classpath via CLASSPATH environment variable - classpath := os.Getenv("CLASSPATH") - if classpath != "" { - classpath += ":" - } - classpath += matches[0] + depsIdx := p.context.Stager.DepsIdx() + runtimePath := fmt.Sprintf("$DEPS_DIR/%s/postgresql_jdbc/%s", depsIdx, filepath.Base(matches[0])) - if err := p.context.Stager.WriteEnvFile("CLASSPATH", classpath); err != nil { - return fmt.Errorf("failed to set CLASSPATH for PostgreSQL JDBC: %w", err) + profileScript := fmt.Sprintf("export CLASSPATH=\"%s${CLASSPATH:+:$CLASSPATH}\"\n", runtimePath) + if err := p.context.Stager.WriteProfileD("postgresql_jdbc.sh", profileScript); err != nil { + return fmt.Errorf("failed to write postgresql_jdbc.sh profile.d script: %w", err) } + p.context.Log.Debug("PostgreSQL JDBC JAR will be added to classpath at runtime: %s", runtimePath) + return nil } diff --git a/src/java/frameworks/spring_auto_reconfiguration.go b/src/java/frameworks/spring_auto_reconfiguration.go index 897ab8a1f..8d4d66885 100644 --- a/src/java/frameworks/spring_auto_reconfiguration.go +++ b/src/java/frameworks/spring_auto_reconfiguration.go @@ -101,17 +101,16 @@ func (s *SpringAutoReconfigurationFramework) Finalize() error { return nil } - // Add to classpath via CLASSPATH environment variable - classpath := os.Getenv("CLASSPATH") - if classpath != "" { - classpath += ":" - } - classpath += matches[0] + depsIdx := s.context.Stager.DepsIdx() + runtimePath := fmt.Sprintf("$DEPS_DIR/%s/spring_auto_reconfiguration/%s", depsIdx, filepath.Base(matches[0])) - if err := s.context.Stager.WriteEnvFile("CLASSPATH", classpath); err != nil { - return fmt.Errorf("failed to set CLASSPATH for Spring Auto-reconfiguration: %w", err) + profileScript := fmt.Sprintf("export CLASSPATH=\"%s${CLASSPATH:+:$CLASSPATH}\"\n", runtimePath) + if err := s.context.Stager.WriteProfileD("spring_auto_reconfiguration.sh", profileScript); err != nil { + return fmt.Errorf("failed to write spring_auto_reconfiguration.sh profile.d script: %w", err) } + s.context.Log.Debug("Spring Auto-reconfiguration JAR will be added to classpath at runtime: %s", runtimePath) + return nil } From 13c265868092821420bb6ffb710a08590a7c6748 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Tue, 10 Mar 2026 13:25:26 +0200 Subject: [PATCH 3/5] Adjust symlink dep creation for tomcat;refactor finalize --- src/java/containers/tomcat.go | 23 +++++++++++++++++++++++ src/java/finalize/finalize.go | 12 ++---------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index be53ad148..84b8bd37a 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -604,6 +604,12 @@ func (t *TomcatContainer) Finalize() error { webInf := filepath.Join(buildDir, "WEB-INF") if _, err := os.Stat(webInf); err == nil { + // the fix name is prefixed with 'zzz' as it is important to be the last script sourced from profile.d + // so that the previous scripts assembling the CLASSPATH variable were sourced previous to it. + if err := t.context.Stager.WriteProfileD("zzz_classpath_symlinks.sh", symlinkScript); err != nil { + return fmt.Errorf("failed to write zzz_classpath_symlinks.sh: %w", err) + } + contextXMLDir := filepath.Dir(contextXMLPath) if err := os.MkdirAll(contextXMLDir, 0755); err != nil { return fmt.Errorf("failed to create context directory: %w", err) @@ -644,3 +650,20 @@ func (t *TomcatContainer) Release() (string, error) { return cmd, nil } + +var symlinkScript = fmt.Sprintf(`#!/bin/bash +set -uo pipefail +TARGET_DIR="$PWD/%s" +CLASSPATH=${CLASSPATH:-} +mkdir -p "$TARGET_DIR" +# Split CLASSPATH on : +IFS=':' read -ra PATHS <<< "$CLASSPATH" +for p in "${PATHS[@]}"; do + # Skip empty entries + [[ -z "$p" ]] && continue + name=$(basename "$p") + link="$TARGET_DIR/$name" + ln -s "$p" "$link" + echo "Created symlink: $link -> $p" +done +`, filepath.Join("WEB-INF", "lib")) diff --git a/src/java/finalize/finalize.go b/src/java/finalize/finalize.go index 665b41344..55802df07 100644 --- a/src/java/finalize/finalize.go +++ b/src/java/finalize/finalize.go @@ -102,7 +102,7 @@ func Run(f *Finalizer) error { } // Finalize frameworks (APM agents, etc.) - if err := f.finalizeFrameworks(); err != nil { + if err := f.finalizeFrameworks(ctx); err != nil { f.Log.Error("Failed to finalize frameworks: %s", err.Error()) return err } @@ -158,17 +158,9 @@ func (f *Finalizer) finalizeJRE() error { } // finalizeFrameworks finalizes framework components (APM agents, etc.) -func (f *Finalizer) finalizeFrameworks() error { +func (f *Finalizer) finalizeFrameworks(ctx *common.Context) error { f.Log.BeginStep("Finalizing frameworks") - ctx := &common.Context{ - Stager: f.Stager, - Manifest: f.Manifest, - Installer: f.Installer, - Log: f.Log, - Command: f.Command, - } - registry := frameworks.NewRegistry(ctx) registry.RegisterStandardFrameworks() From cf94d9ee9d6f774859ae632519bec3efc0a77ede Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Tue, 10 Mar 2026 16:16:16 +0200 Subject: [PATCH 4/5] Adjust symlink dep creation --- src/java/containers/container.go | 17 +++++++++++++++++ src/java/containers/spring_boot.go | 5 ++++- src/java/containers/tomcat.go | 19 +------------------ 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/java/containers/container.go b/src/java/containers/container.go index 6a5b0a1c2..8feed5dd5 100644 --- a/src/java/containers/container.go +++ b/src/java/containers/container.go @@ -107,3 +107,20 @@ func (r *Registry) RegisterStandardContainers() { r.Register(NewDistZipContainer(r.context)) r.Register(NewJavaMainContainer(r.context)) } + +var symlinkScript = `#!/bin/bash +set -uo pipefail +TARGET_DIR="$PWD/%s" +CLASSPATH=${CLASSPATH:-} +mkdir -p "$TARGET_DIR" +# Split CLASSPATH on : +IFS=':' read -ra PATHS <<< "$CLASSPATH" +for p in "${PATHS[@]}"; do + # Skip empty entries + [[ -z "$p" ]] && continue + name=$(basename "$p") + link="$TARGET_DIR/$name" + ln -s "$p" "$link" + echo "Created symlink: $link -> $p" +done +` diff --git a/src/java/containers/spring_boot.go b/src/java/containers/spring_boot.go index cabc4d4ef..bc9091dcb 100644 --- a/src/java/containers/spring_boot.go +++ b/src/java/containers/spring_boot.go @@ -1,8 +1,8 @@ package containers import ( - "github.com/cloudfoundry/java-buildpack/src/java/common" "fmt" + "github.com/cloudfoundry/java-buildpack/src/java/common" "os" "path/filepath" "strings" @@ -234,6 +234,9 @@ func (s *SpringBootContainer) Release() (string, error) { bootInf := filepath.Join(buildDir, "BOOT-INF") if _, err := os.Stat(bootInf); err == nil { // Verify this is actually a Spring Boot application + if err := s.context.Stager.WriteProfileD("zzz_classpath_symlinks.sh", fmt.Sprintf(symlinkScript, filepath.Join("BOOT-INF", "lib"))); err != nil { + return "", fmt.Errorf("failed to write zzz_classpath_symlinks.sh: %w", err) + } if s.isSpringBootExplodedJar(buildDir) { // True Spring Boot exploded JAR - use JarLauncher // Determine the correct JarLauncher class name based on Spring Boot version diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index 84b8bd37a..92fa69d4d 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -606,7 +606,7 @@ func (t *TomcatContainer) Finalize() error { if _, err := os.Stat(webInf); err == nil { // the fix name is prefixed with 'zzz' as it is important to be the last script sourced from profile.d // so that the previous scripts assembling the CLASSPATH variable were sourced previous to it. - if err := t.context.Stager.WriteProfileD("zzz_classpath_symlinks.sh", symlinkScript); err != nil { + if err := t.context.Stager.WriteProfileD("zzz_classpath_symlinks.sh", fmt.Sprintf(symlinkScript, filepath.Join("WEB-INF", "lib"))); err != nil { return fmt.Errorf("failed to write zzz_classpath_symlinks.sh: %w", err) } @@ -650,20 +650,3 @@ func (t *TomcatContainer) Release() (string, error) { return cmd, nil } - -var symlinkScript = fmt.Sprintf(`#!/bin/bash -set -uo pipefail -TARGET_DIR="$PWD/%s" -CLASSPATH=${CLASSPATH:-} -mkdir -p "$TARGET_DIR" -# Split CLASSPATH on : -IFS=':' read -ra PATHS <<< "$CLASSPATH" -for p in "${PATHS[@]}"; do - # Skip empty entries - [[ -z "$p" ]] && continue - name=$(basename "$p") - link="$TARGET_DIR/$name" - ln -s "$p" "$link" - echo "Created symlink: $link -> $p" -done -`, filepath.Join("WEB-INF", "lib")) From 61c927949f5ea53e78564873850451d3a6b55950 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Tue, 10 Mar 2026 16:40:27 +0200 Subject: [PATCH 5/5] Adjust comments --- src/java/containers/spring_boot.go | 3 +++ src/java/containers/tomcat.go | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/java/containers/spring_boot.go b/src/java/containers/spring_boot.go index bc9091dcb..303992e1d 100644 --- a/src/java/containers/spring_boot.go +++ b/src/java/containers/spring_boot.go @@ -234,6 +234,9 @@ func (s *SpringBootContainer) Release() (string, error) { bootInf := filepath.Join(buildDir, "BOOT-INF") if _, err := os.Stat(bootInf); err == nil { // Verify this is actually a Spring Boot application + + // the script name is prefixed with 'zzz' as it is important to be the last script sourced from profile.d + // so that the previous scripts assembling the CLASSPATH variable(left from frameworks) are sourced previous to it. if err := s.context.Stager.WriteProfileD("zzz_classpath_symlinks.sh", fmt.Sprintf(symlinkScript, filepath.Join("BOOT-INF", "lib"))); err != nil { return "", fmt.Errorf("failed to write zzz_classpath_symlinks.sh: %w", err) } diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index 92fa69d4d..f264133ba 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -604,8 +604,8 @@ func (t *TomcatContainer) Finalize() error { webInf := filepath.Join(buildDir, "WEB-INF") if _, err := os.Stat(webInf); err == nil { - // the fix name is prefixed with 'zzz' as it is important to be the last script sourced from profile.d - // so that the previous scripts assembling the CLASSPATH variable were sourced previous to it. + // the script name is prefixed with 'zzz' as it is important to be the last script sourced from profile.d + // so that the previous scripts assembling the CLASSPATH variable(left from frameworks) are sourced previous to it. if err := t.context.Stager.WriteProfileD("zzz_classpath_symlinks.sh", fmt.Sprintf(symlinkScript, filepath.Join("WEB-INF", "lib"))); err != nil { return fmt.Errorf("failed to write zzz_classpath_symlinks.sh: %w", err) }