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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/java/common/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ func DetermineJavaVersion(javaHome string) (int, error) {
releaseFile := filepath.Join(javaHome, "release")
content, err := os.ReadFile(releaseFile)
if err != nil {
// Default to Java 17 if release file is missing
if os.IsNotExist(err) {
return 17, nil
}
return 0, fmt.Errorf("failed to read release file: %w", err)
}

Expand Down
69 changes: 35 additions & 34 deletions src/java/containers/tomcat.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,45 +64,46 @@ func (t *TomcatContainer) Supply() error {

if javaHome != "" {
javaMajorVersion, versionErr := common.DetermineJavaVersion(javaHome)
if versionErr == nil {
tomcatVersion := DetermineTomcatVersion(t.config.Tomcat.Version)
t.context.Log.Debug("Detected Java major version: %d", javaMajorVersion)

// Select Tomcat version pattern based on Java version
var versionPattern string
if tomcatVersion == "" {
t.context.Log.Info("Tomcat version not specified")
if javaMajorVersion >= 11 {
// Java 11+: Use Tomcat 10.x (Jakarta EE 9+)
versionPattern = "10.x"
t.context.Log.Info("Using Tomcat 10.x for Java %d", javaMajorVersion)
} else {
// Java 8-10: Use Tomcat 9.x (Java EE 8)
versionPattern = "9.x"
t.context.Log.Info("Using Tomcat 9.x for Java %d", javaMajorVersion)
}
} else {
versionPattern = tomcatVersion
t.context.Log.Info("Using Tomcat %s for Java %d", versionPattern, javaMajorVersion)
}
if versionErr != nil {
t.context.Log.Warning("Unable to determine Java version: %s (defaulting to 17)", versionErr.Error())
javaMajorVersion = 17
}

if strings.HasPrefix(versionPattern, "10.") && javaMajorVersion < 11 {
return fmt.Errorf("Tomcat 10.x requires Java 11+, but Java %d detected", javaMajorVersion)
tomcatVersion := DetermineTomcatVersion(t.config.Tomcat.Version)
t.context.Log.Debug("Detected Java major version: %d", javaMajorVersion)

// Select Tomcat version pattern based on Java version
var versionPattern string
if tomcatVersion == "" {
t.context.Log.Info("Tomcat version not specified")
if javaMajorVersion >= 11 {
// Java 11+: Use Tomcat 10.x (Jakarta EE 9+)
versionPattern = "10.x"
t.context.Log.Info("Using Tomcat 10.x for Java %d", javaMajorVersion)
} else {
// Java 8-10: Use Tomcat 9.x (Java EE 8)
versionPattern = "9.x"
t.context.Log.Info("Using Tomcat 9.x for Java %d", javaMajorVersion)
}
} else {
versionPattern = tomcatVersion
t.context.Log.Info("Using Tomcat %s for Java %d", versionPattern, javaMajorVersion)
}

// Resolve the version pattern to actual version using libbuildpack
allVersions := t.context.Manifest.AllDependencyVersions("tomcat")
resolvedVersion, err := libbuildpack.FindMatchingVersion(versionPattern, allVersions)
if err != nil {
return fmt.Errorf("tomcat version resolution error for pattern %q: %w", versionPattern, err)
}
if strings.HasPrefix(versionPattern, "10.") && javaMajorVersion < 11 {
return fmt.Errorf("Tomcat 10.x requires Java 11+, but Java %d detected", javaMajorVersion)
}

dep.Name = "tomcat"
dep.Version = resolvedVersion
t.context.Log.Debug("Resolved Tomcat version pattern '%s' to %s", versionPattern, resolvedVersion)
} else {
t.context.Log.Warning("Unable to determine Java version: %s", versionErr.Error())
// Resolve the version pattern to actual version using libbuildpack
allVersions := t.context.Manifest.AllDependencyVersions("tomcat")
resolvedVersion, err := libbuildpack.FindMatchingVersion(versionPattern, allVersions)
if err != nil {
return fmt.Errorf("tomcat version resolution error for pattern %q: %w", versionPattern, err)
}

dep.Name = "tomcat"
dep.Version = resolvedVersion
t.context.Log.Debug("Resolved Tomcat version pattern '%s' to %s", versionPattern, resolvedVersion)
}

// Fallback to default version if we couldn't determine Java version
Expand Down
245 changes: 245 additions & 0 deletions src/java/jres/base_jre.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
package jres

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/cloudfoundry/java-buildpack/src/java/common"
)

// BaseJRE provides the shared implementation for all standard JRE providers.
// Variation between JREs is injected via fields — implementers never override
// Supply/Finalize, avoiding the "forgot to call base" footgun.
type BaseJRE struct {
ctx *common.Context
jreDir string
version string
javaHome string
memoryCalc *MemoryCalculator
jvmkill *JVMKillAgent
installedVersion string

jreName string
jreKey string
dirPrefixes []string
dirExacts []string
installErrNote string

extraFinalizeOpts func() string
}

func newBaseJRE(ctx *common.Context, jreName, jreKey string, dirPrefixes, dirExacts []string, installErrNote string) BaseJRE {
return BaseJRE{
ctx: ctx,
jreDir: filepath.Join(ctx.Stager.DepDir(), "jre"),
jreName: jreName,
jreKey: jreKey,
dirPrefixes: dirPrefixes,
dirExacts: dirExacts,
installErrNote: installErrNote,
}
}

func (b *BaseJRE) Name() string {
return b.jreName
}

func (b *BaseJRE) Detect() (bool, error) {
return DetectJREByEnv(b.jreKey), nil
}

func (b *BaseJRE) Supply() error {
b.ctx.Log.BeginStep("Installing %s", b.jreName)

dep, err := GetJREVersion(b.ctx, b.jreKey)
if err != nil {
return fmt.Errorf("failed to determine %s version from manifest: %w", b.jreName, err)
}

b.version = dep.Version
b.ctx.Log.Info("Installing %s (%s)", b.jreName, b.version)

if err := b.ctx.Installer.InstallDependency(dep, b.jreDir); err != nil {
if b.installErrNote != "" {
return fmt.Errorf("failed to install %s: %w %s", b.jreName, err, b.installErrNote)
}
return fmt.Errorf("failed to install %s: %w", b.jreName, err)
}

javaHome, err := b.findJavaHome()
if err != nil {
return fmt.Errorf("failed to find JAVA_HOME: %w", err)
}
b.javaHome = javaHome
b.installedVersion = b.version

if err := b.writeProfileDScript(); err != nil {
b.ctx.Log.Warning("Could not write java.sh profile.d script: %s", err.Error())
} else {
b.ctx.Log.Debug("Created profile.d script: java.sh")
}

if err := b.ctx.Stager.WriteEnvFile("JAVA_HOME", javaHome); err != nil {
b.ctx.Log.Warning("Could not write JAVA_HOME env file: %s", err.Error())
}

javaBin := filepath.Join(javaHome, "bin", "java")
if err := b.ctx.Stager.AddBinDependencyLink(javaBin, "java"); err != nil {
b.ctx.Log.Warning("Could not add java bin dependency link: %s", err.Error())
}

libDir := filepath.Join(javaHome, "lib")
if _, err := os.Stat(libDir); err == nil {
if err := b.ctx.Stager.LinkDirectoryInDepDir(libDir, "lib"); err != nil {
b.ctx.Log.Warning("Could not link JRE lib directory: %s", err.Error())
}
}

javaMajorVersion, err := common.DetermineJavaVersion(javaHome)
if err != nil {
b.ctx.Log.Warning("Could not determine Java version: %s", err.Error())
javaMajorVersion = 17
}
b.ctx.Log.Info("Detected Java major version: %d", javaMajorVersion)

b.jvmkill = NewJVMKillAgent(b.ctx, b.jreDir, b.version)
if err := b.jvmkill.Supply(); err != nil {
b.ctx.Log.Warning("Failed to install JVMKill agent: %s (continuing)", err.Error())
}

b.memoryCalc = NewMemoryCalculator(b.ctx, b.jreDir, b.version, javaMajorVersion, b.jreKey)
if err := b.memoryCalc.Supply(); err != nil {
b.ctx.Log.Warning("Failed to install Memory Calculator: %s (continuing)", err.Error())
}

b.ctx.Log.Info("%s installation complete", b.jreName)
return nil
}

func (b *BaseJRE) Finalize() error {
b.ctx.Log.BeginStep("Finalizing %s configuration", b.jreName)

if b.javaHome == "" {
javaHome, err := b.findJavaHome()
if err != nil {
b.ctx.Log.Warning("Failed to find JAVA_HOME: %s", err.Error())
} else {
b.javaHome = javaHome
}
}

if b.javaHome != "" {
if err := os.Setenv("JAVA_HOME", b.javaHome); err != nil {
b.ctx.Log.Warning("Failed to set JAVA_HOME environment variable: %s", err.Error())
} else {
b.ctx.Log.Debug("Set JAVA_HOME=%s", b.javaHome)
}
}

javaMajorVersion := 17
if b.javaHome != "" {
if ver, err := common.DetermineJavaVersion(b.javaHome); err != nil {
b.ctx.Log.Warning("Could not determine Java version: %s (defaulting to %d)", err.Error(), javaMajorVersion)
} else {
javaMajorVersion = ver
}
}

if b.jvmkill == nil {
b.jvmkill = NewJVMKillAgent(b.ctx, b.jreDir, b.version)
}
if err := b.jvmkill.Finalize(); err != nil {
b.ctx.Log.Warning("Failed to finalize JVMKill agent: %s", err.Error())
}

baseOpts := []string{
"-Djava.io.tmpdir=$TMPDIR",
"-XX:ActiveProcessorCount=$(nproc)",
}
if err := WriteJavaOpts(b.ctx, strings.Join(baseOpts, " ")); err != nil {
b.ctx.Log.Warning("Failed to write base JAVA_OPTS: %s", err.Error())
}

if b.memoryCalc == nil {
b.memoryCalc = NewMemoryCalculator(b.ctx, b.jreDir, b.version, javaMajorVersion, b.jreKey)
}
if err := b.memoryCalc.Finalize(); err != nil {
b.ctx.Log.Warning("Failed to finalize Memory Calculator: %s", err.Error())
}

if b.extraFinalizeOpts != nil {
if extraOpts := b.extraFinalizeOpts(); extraOpts != "" {
if err := WriteJavaOpts(b.ctx, extraOpts); err != nil {
b.ctx.Log.Warning("Failed to write %s JAVA_OPTS: %s", b.jreName, err.Error())
}
}
}

b.ctx.Log.Info("%s finalization complete", b.jreName)
return nil
}

func (b *BaseJRE) JavaHome() string {
return b.javaHome
}

func (b *BaseJRE) Version() string {
return b.installedVersion
}

func (b *BaseJRE) MemoryCalculatorCommand() string {
if b.memoryCalc == nil {
return ""
}
return b.memoryCalc.GetCalculatorCommand()
}

func (b *BaseJRE) findJavaHome() (string, error) {
entries, err := os.ReadDir(b.jreDir)
if err != nil {
return "", fmt.Errorf("failed to read JRE directory: %w", err)
}

for _, entry := range entries {
if !entry.IsDir() {
continue
}

name := entry.Name()
matches := false
for _, prefix := range b.dirPrefixes {
if strings.HasPrefix(name, prefix) {
matches = true
break
}
}
if !matches {
for _, exact := range b.dirExacts {
if name == exact {
matches = true
break
}
}
}
if !matches {
continue
}

path := filepath.Join(b.jreDir, name)
if _, err := os.Stat(filepath.Join(path, "bin", "java")); err == nil {
return path, nil
}
}

if _, err := os.Stat(filepath.Join(b.jreDir, "bin", "java")); err == nil {
return b.jreDir, nil
}

return "", fmt.Errorf("could not find valid JAVA_HOME in %s", b.jreDir)
}

func (b *BaseJRE) writeProfileDScript() error {
return WriteJavaHomeProfileD(b.ctx, b.jreDir, b.javaHome)
}
Loading