Skip to content

Commit 7f113cd

Browse files
committed
fix(java-cfenv): add the mirrored jar to the classpath + guard the cloud-profile artifact
main now ships java-cfenv-all (the aggregate module carrying CloudProfileApplicationListener), mirrored as java-cfenv_<ver>_<stack>_<sha>.jar (underscores). But Finalize globbed `java-cfenv-*.jar` (hyphen), which does not match the underscore mirror name, so the jar was installed under deps/ but never added to the profile.d CLASSPATH -- the `cloud` profile still would not activate at runtime. - Finalize: glob `java-cfenv*.jar` so both the Maven name (java-cfenv-all-<ver>.jar) and the CF mirror name match. - Add a Finalize test using the real underscore mirror filename. - Add a network-gated artifact test (build tag cfenv_artifact) asserting the shipped jar registers CloudProfileApplicationListener in spring.factories. Refs #1349.
1 parent bb381b5 commit 7f113cd

3 files changed

Lines changed: 190 additions & 1 deletion

File tree

src/java/frameworks/java_cf_env.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ func (j *JavaCfEnvFramework) Supply() error {
8686
func (j *JavaCfEnvFramework) Finalize() error {
8787
// Add the JAR to additional libraries (classpath)
8888
javaCfEnvDir := filepath.Join(j.context.Stager.DepDir(), "java_cf_env")
89-
jarPattern := filepath.Join(javaCfEnvDir, "java-cfenv-*.jar")
89+
// Match both the Maven name (java-cfenv-all-<ver>.jar) and the CF mirror name
90+
// (java-cfenv_<ver>_<stack>_<sha>.jar, underscores) — a hyphen-only glob misses the latter.
91+
jarPattern := filepath.Join(javaCfEnvDir, "java-cfenv*.jar")
9092

9193
matches, err := filepath.Glob(jarPattern)
9294
if err != nil || len(matches) == 0 {
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
//go:build cfenv_artifact
2+
3+
// This test verifies a *necessary condition* for the buildpack's automatic "cloud"
4+
// Spring profile behaviour: the java-cfenv artifact the manifest ships must register
5+
// io.pivotal.cfenv.profile.CloudProfileApplicationListener as a Spring ApplicationListener
6+
// (via META-INF/spring.factories). That listener lives only in the java-cfenv-all module;
7+
// the bare java-cfenv core module does not carry it, which silently drops the "cloud"
8+
// profile (see cloudfoundry/java-buildpack#1349).
9+
//
10+
// It downloads each shipped jar, verifies its SHA-256 against the manifest (which also
11+
// guards against duplicate/mismatched mirror entries), then inspects spring.factories.
12+
//
13+
// It is network-bound, so it is excluded from the default unit suite by the cfenv_artifact
14+
// build tag. Run explicitly:
15+
//
16+
// go test -tags cfenv_artifact ./src/java/frameworks/
17+
package frameworks_test
18+
19+
import (
20+
"archive/zip"
21+
"crypto/sha256"
22+
"encoding/hex"
23+
"io"
24+
"net/http"
25+
"os"
26+
"path/filepath"
27+
"strings"
28+
"time"
29+
30+
. "github.com/onsi/ginkgo/v2"
31+
. "github.com/onsi/gomega"
32+
33+
"gopkg.in/yaml.v2"
34+
)
35+
36+
const cloudProfileListener = "io.pivotal.cfenv.profile.CloudProfileApplicationListener"
37+
38+
type manifestDependency struct {
39+
Name string `yaml:"name"`
40+
Version string `yaml:"version"`
41+
URI string `yaml:"uri"`
42+
SHA256 string `yaml:"sha256"`
43+
}
44+
45+
type manifestFile struct {
46+
Dependencies []manifestDependency `yaml:"dependencies"`
47+
}
48+
49+
var _ = Describe("Java CF Env artifact", func() {
50+
var deps []manifestDependency
51+
52+
BeforeEach(func() {
53+
manifestPath, err := filepath.Abs(filepath.Join("..", "..", "..", "manifest.yml"))
54+
Expect(err).NotTo(HaveOccurred())
55+
56+
raw, err := os.ReadFile(manifestPath)
57+
Expect(err).NotTo(HaveOccurred())
58+
59+
var m manifestFile
60+
Expect(yaml.Unmarshal(raw, &m)).To(Succeed())
61+
62+
for _, d := range m.Dependencies {
63+
if d.Name == "java-cfenv" && d.URI != "" {
64+
deps = append(deps, d)
65+
}
66+
}
67+
Expect(deps).NotTo(BeEmpty(), "no java-cfenv dependency entries with a uri found in manifest.yml")
68+
})
69+
70+
It("ships a jar that registers the cloud-profile ApplicationListener", func() {
71+
for _, dep := range deps {
72+
By("checking java-cfenv " + dep.Version)
73+
74+
jar := downloadToTemp(dep.URI)
75+
defer os.Remove(jar)
76+
77+
Expect(fileSHA256(jar)).To(Equal(dep.SHA256),
78+
"java-cfenv %s: downloaded bytes do not match manifest sha256 (%s)", dep.Version, dep.URI)
79+
80+
factories, ok := readZipEntry(jar, "META-INF/spring.factories")
81+
Expect(ok).To(BeTrue(),
82+
"java-cfenv %s: jar has no META-INF/spring.factories — this is the bare core module, "+
83+
"which cannot auto-activate the 'cloud' profile; ship java-cfenv-all instead", dep.Version)
84+
85+
Expect(factories).To(ContainSubstring(cloudProfileListener),
86+
"java-cfenv %s: spring.factories does not register %s — the 'cloud' profile will not be "+
87+
"activated automatically; ship java-cfenv-all instead", dep.Version, cloudProfileListener)
88+
89+
Expect(registersAsApplicationListener(factories, cloudProfileListener)).To(BeTrue(),
90+
"java-cfenv %s: %s is present but not registered under "+
91+
"org.springframework.context.ApplicationListener", dep.Version, cloudProfileListener)
92+
}
93+
})
94+
})
95+
96+
// registersAsApplicationListener reports whether class is listed under the
97+
// org.springframework.context.ApplicationListener key, accounting for backslash
98+
// line continuations used in spring.factories files.
99+
func registersAsApplicationListener(factories, class string) bool {
100+
const key = "org.springframework.context.ApplicationListener"
101+
joined := strings.ReplaceAll(factories, "\\\n", "")
102+
for _, line := range strings.Split(joined, "\n") {
103+
line = strings.TrimSpace(line)
104+
if strings.HasPrefix(line, key+"=") {
105+
return strings.Contains(line, class)
106+
}
107+
}
108+
return false
109+
}
110+
111+
func downloadToTemp(uri string) string {
112+
GinkgoHelper()
113+
114+
client := &http.Client{Timeout: 60 * time.Second}
115+
resp, err := client.Get(uri)
116+
Expect(err).NotTo(HaveOccurred(), "download %s", uri)
117+
defer resp.Body.Close()
118+
Expect(resp.StatusCode).To(Equal(http.StatusOK), "download %s returned %d", uri, resp.StatusCode)
119+
120+
f, err := os.CreateTemp("", "java-cfenv-*.jar")
121+
Expect(err).NotTo(HaveOccurred())
122+
defer f.Close()
123+
124+
_, err = io.Copy(f, resp.Body)
125+
Expect(err).NotTo(HaveOccurred())
126+
127+
return f.Name()
128+
}
129+
130+
func fileSHA256(path string) string {
131+
GinkgoHelper()
132+
133+
f, err := os.Open(path)
134+
Expect(err).NotTo(HaveOccurred())
135+
defer f.Close()
136+
137+
h := sha256.New()
138+
_, err = io.Copy(h, f)
139+
Expect(err).NotTo(HaveOccurred())
140+
141+
return hex.EncodeToString(h.Sum(nil))
142+
}
143+
144+
func readZipEntry(jarPath, entry string) (string, bool) {
145+
GinkgoHelper()
146+
147+
r, err := zip.OpenReader(jarPath)
148+
Expect(err).NotTo(HaveOccurred())
149+
defer r.Close()
150+
151+
for _, f := range r.File {
152+
if f.Name == entry {
153+
rc, err := f.Open()
154+
Expect(err).NotTo(HaveOccurred())
155+
defer rc.Close()
156+
157+
data, err := io.ReadAll(rc)
158+
Expect(err).NotTo(HaveOccurred())
159+
return string(data), true
160+
}
161+
}
162+
return "", false
163+
}

src/java/frameworks/java_cf_env_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,5 +272,29 @@ var _ = Describe("Java CF Env", func() {
272272
Expect(string(content)).To(ContainSubstring("java-cfenv-2.5.0.jar"))
273273
})
274274
})
275+
276+
Context("when the mirrored, underscore-named JAR is installed", func() {
277+
// The CF dependency mirror names the artifact
278+
// java-cfenv_<version>_<stack>_<sha>.jar (underscores), not the
279+
// hyphenated Maven name. Finalize must still add it to the classpath.
280+
BeforeEach(func() {
281+
javaCfEnvDir := filepath.Join(depsDir, "0", "java_cf_env")
282+
Expect(os.MkdirAll(javaCfEnvDir, 0755)).To(Succeed())
283+
Expect(os.WriteFile(
284+
filepath.Join(javaCfEnvDir, "java-cfenv_3.5.1_linux_noarch_any-stack_696225e3.jar"),
285+
[]byte("fake jar"),
286+
0644,
287+
)).To(Succeed())
288+
})
289+
290+
It("adds the mirrored JAR to the profile.d CLASSPATH", func() {
291+
Expect(fw.Finalize()).To(Succeed())
292+
scriptPath := filepath.Join(depsDir, "0", "profile.d", "java_cf_env.sh")
293+
Expect(scriptPath).To(BeAnExistingFile())
294+
content, err := os.ReadFile(scriptPath)
295+
Expect(err).NotTo(HaveOccurred())
296+
Expect(string(content)).To(ContainSubstring("java-cfenv_3.5.1_linux_noarch_any-stack_696225e3.jar"))
297+
})
298+
})
275299
})
276300
})

0 commit comments

Comments
 (0)