Skip to content

Commit 1342e49

Browse files
committed
Provide a condition for detecting war deployments
Closes gh-19421
1 parent 9aae072 commit 1342e49

File tree

19 files changed

+315
-6
lines changed

19 files changed

+315
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.condition;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.context.annotation.Conditional;
26+
27+
/**
28+
* {@link Conditional @Conditional} that matches when the application is a traditional WAR
29+
* deployment. For applications with embedded servers, this condition will return false.
30+
*
31+
* @author Madhura Bhave
32+
* @since 2.3.0
33+
*/
34+
@Target({ ElementType.TYPE, ElementType.METHOD })
35+
@Retention(RetentionPolicy.RUNTIME)
36+
@Documented
37+
@Conditional(OnWarDeploymentCondition.class)
38+
public @interface ConditionalOnWarDeployment {
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.condition;
18+
19+
import javax.servlet.ServletContext;
20+
21+
import org.springframework.context.annotation.Condition;
22+
import org.springframework.context.annotation.ConditionContext;
23+
import org.springframework.core.io.ResourceLoader;
24+
import org.springframework.core.type.AnnotatedTypeMetadata;
25+
import org.springframework.web.context.WebApplicationContext;
26+
27+
/**
28+
* {@link Condition} that checks if the application is running as a traditional war
29+
* deployment.
30+
*
31+
* @author Madhura Bhave
32+
*/
33+
class OnWarDeploymentCondition extends SpringBootCondition {
34+
35+
@Override
36+
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
37+
ResourceLoader resourceLoader = context.getResourceLoader();
38+
if (resourceLoader instanceof WebApplicationContext) {
39+
WebApplicationContext applicationContext = (WebApplicationContext) resourceLoader;
40+
ServletContext servletContext = applicationContext.getServletContext();
41+
if (servletContext != null) {
42+
return ConditionOutcome.match("Application is deployed as a WAR file.");
43+
}
44+
}
45+
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnWarDeployment.class)
46+
.because("the application is not deployed as a WAR file."));
47+
}
48+
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.condition;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
22+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
23+
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
24+
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext;
25+
import org.springframework.context.annotation.Bean;
26+
import org.springframework.context.annotation.Configuration;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
/**
31+
* Tests for {@link ConditionalOnWarDeployment @ConditionalOnWarDeployment}.
32+
*
33+
* @author Madhura Bhave
34+
*/
35+
class ConditionalOnWarDeploymentTests {
36+
37+
@Test
38+
void nonWebApplicationShouldNotMatch() {
39+
ApplicationContextRunner contextRunner = new ApplicationContextRunner();
40+
contextRunner.withUserConfiguration(TestConfiguration.class)
41+
.run((context) -> assertThat(context).doesNotHaveBean("forWar"));
42+
}
43+
44+
@Test
45+
void reactiveWebApplicationShouldNotMatch() {
46+
ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner();
47+
contextRunner.withUserConfiguration(TestConfiguration.class)
48+
.run((context) -> assertThat(context).doesNotHaveBean("forWar"));
49+
}
50+
51+
@Test
52+
void embeddedServletWebApplicationShouldNotMatch() {
53+
WebApplicationContextRunner contextRunner = new WebApplicationContextRunner(
54+
AnnotationConfigServletWebApplicationContext::new);
55+
contextRunner.withUserConfiguration(TestConfiguration.class)
56+
.run((context) -> assertThat(context).doesNotHaveBean("forWar"));
57+
}
58+
59+
@Test
60+
void warDeployedServletWebApplicationShouldMatch() {
61+
// sets a mock servletContext before context refresh which is what the
62+
// SpringBootServletInitializer does for WAR deployments.
63+
WebApplicationContextRunner contextRunner = new WebApplicationContextRunner();
64+
contextRunner.withUserConfiguration(TestConfiguration.class)
65+
.run((context) -> assertThat(context).hasBean("forWar"));
66+
}
67+
68+
@Configuration(proxyBeanMethods = false)
69+
@ConditionalOnWarDeployment
70+
static class TestConfiguration {
71+
72+
@Bean
73+
String forWar() {
74+
return "forWar";
75+
}
76+
77+
}
78+
79+
}

spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7606,6 +7606,9 @@ The `@ConditionalOnWebApplication` and `@ConditionalOnNotWebApplication` annotat
76067606
A servlet-based web application is any application that uses a Spring `WebApplicationContext`, defines a `session` scope, or has a `ConfigurableWebEnvironment`.
76077607
A reactive web application is any application that uses a `ReactiveWebApplicationContext`, or has a `ConfigurableReactiveWebEnvironment`.
76087608

7609+
The `@ConditionalOnWarDeployment` annotation lets configuration be included depending on whether the application is a traditional WAR application that is deployed to a container.
7610+
This condition will not match for applications that are run with an embedded server.
7611+
76097612

76107613

76117614
[[boot-features-spel-conditions]]

spring-boot-tests/spring-boot-deployment-tests/build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies {
1010
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web")) {
1111
exclude group: "org.hibernate.validator"
1212
}
13+
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator"))
1314

1415
intTestImplementation(enforcedPlatform(project(path: ":spring-boot-project:spring-boot-parent")))
1516
intTestImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
@@ -20,8 +21,6 @@ dependencies {
2021
intTestImplementation("org.springframework:spring-web")
2122

2223
providedRuntime(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-tomcat"))
23-
24-
runtimeOnly(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator"))
2524
}
2625

2726
intTest {

spring-boot-tests/spring-boot-deployment-tests/src/intTest/java/sample/DeploymentIntegrationTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ void health(DeployedApplication application) {
6666
});
6767
}
6868

69+
@ParameterizedTest
70+
@MethodSource("deployedApplications")
71+
void conditionalOnWarShouldBeTrue(DeployedApplication application) throws Exception {
72+
application.test((rest) -> {
73+
ResponseEntity<String> response = rest.getForEntity("/actuator/war", String.class);
74+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
75+
assertThat(response.getBody()).isEqualTo("{\"hello\":\"world\"}");
76+
});
77+
}
78+
6979
static List<DeployedApplication> deployedApplications() {
7080
return Arrays.asList(new DeployedApplication("open-liberty:19.0.0.9-webProfile8", "/config/dropins", 9080),
7181
new DeployedApplication("tomcat:9.0.29-jdk8-openjdk", "/usr/local/tomcat/webapps", 8080),

spring-boot-tests/spring-boot-deployment-tests/src/main/java/sample/DeploymentTestApplication.java renamed to spring-boot-tests/spring-boot-deployment-tests/src/main/java/sample/app/DeploymentTestApplication.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package sample;
17+
package sample.app;
1818

1919
import org.springframework.boot.autoconfigure.SpringBootApplication;
2020
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

spring-boot-tests/spring-boot-deployment-tests/src/main/java/sample/SampleController.java renamed to spring-boot-tests/spring-boot-deployment-tests/src/main/java/sample/app/SampleController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package sample;
17+
package sample.app;
1818

1919
import org.springframework.web.bind.annotation.GetMapping;
2020
import org.springframework.web.bind.annotation.RestController;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package sample.autoconfig;
18+
19+
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
20+
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWarDeployment;
22+
import org.springframework.context.annotation.Bean;
23+
import org.springframework.context.annotation.Configuration;
24+
25+
@ConditionalOnWarDeployment
26+
@Configuration
27+
public class ExampleAutoConfiguration {
28+
29+
@Bean
30+
public TestEndpoint testEndpoint() {
31+
return new TestEndpoint();
32+
}
33+
34+
@Endpoint(id = "war")
35+
static class TestEndpoint {
36+
37+
@ReadOperation
38+
String hello() {
39+
return "{\"hello\":\"world\"}";
40+
}
41+
42+
}
43+
44+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2+
sample.autoconfig.ExampleAutoConfiguration
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
management.endpoints.web.exposure.include: '*'

spring-boot-tests/spring-boot-integration-tests/spring-boot-server-tests/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ configurations {
1111

1212
dependencies {
1313
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
14+
testImplementation(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
1415
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
1516
testImplementation("com.samskivert:jmustache")
1617
testImplementation("jakarta.servlet:jakarta.servlet-api")
@@ -25,6 +26,7 @@ dependencies {
2526
testRepository(project(path: ":spring-boot-project:spring-boot-dependencies", configuration: "mavenRepository"))
2627
testRepository(project(path: ":spring-boot-project:spring-boot-tools:spring-boot-maven-plugin", configuration: "mavenRepository"))
2728
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter", configuration: "mavenRepository"))
29+
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator", configuration: "mavenRepository"))
2830
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-jetty", configuration: "mavenRepository"))
2931
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-parent", configuration: "mavenRepository"))
3032
testRepository(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-tomcat", configuration: "mavenRepository"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.autoconfig;
18+
19+
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
20+
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWarDeployment;
22+
import org.springframework.context.annotation.Bean;
23+
import org.springframework.context.annotation.Configuration;
24+
25+
@ConditionalOnWarDeployment
26+
@Configuration
27+
public class ExampleAutoConfiguration {
28+
29+
@Bean
30+
public TestEndpoint testEndpoint() {
31+
return new TestEndpoint();
32+
}
33+
34+
@Endpoint(id = "war")
35+
static class TestEndpoint {
36+
37+
@ReadOperation
38+
String hello() {
39+
return "{\"hello\":\"world\"}";
40+
}
41+
42+
}
43+
44+
}

spring-boot-tests/spring-boot-integration-tests/spring-boot-server-tests/src/test/java/org/springframework/boot/context/embedded/ApplicationBuilder.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,23 @@ private void copyApplicationSource(File appFolder) throws IOException {
146146
srcMainWebapp.mkdirs();
147147
FileCopyUtils.copy("webapp resource", new FileWriter(new File(srcMainWebapp, "webapp-resource.txt")));
148148
}
149+
copyAutoConfigurationFiles(appFolder);
150+
return;
151+
}
152+
153+
private void copyAutoConfigurationFiles(File appFolder) throws IOException {
154+
File autoConfigPackage = new File(appFolder, "src/main/java/com/autoconfig");
155+
autoConfigPackage.mkdirs();
156+
FileCopyUtils.copy(new File("src/test/java/com/autoconfig/ExampleAutoConfiguration.java"),
157+
new File(autoConfigPackage, "ExampleAutoConfiguration.java"));
158+
File srcMainResources = new File(appFolder, "src/main/resources");
159+
srcMainResources.mkdirs();
160+
File metaInf = new File(srcMainResources, "META-INF");
161+
metaInf.mkdirs();
162+
FileCopyUtils.copy(new File("src/test/resources/META-INF/spring.factories"),
163+
new File(metaInf, "spring.factories"));
164+
FileCopyUtils.copy(new File("src/test/resources/application.yml"),
165+
new File(srcMainResources, "application.yml"));
149166
}
150167

151168
private void packageApplication(File appFolder, File settingsXml) throws MavenInvocationException {

spring-boot-tests/spring-boot-integration-tests/spring-boot-server-tests/src/test/java/org/springframework/boot/context/embedded/EmbeddedServletContainerJarPackagingIntegrationTests.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,10 @@ void launcherIsNotAvailableViaHttp(RestTemplate rest) {
7979
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
8080
}
8181

82+
@TestTemplate
83+
void conditionalOnWarDeploymentBeanIsNotAvailableForEmbeddedServer(RestTemplate rest) {
84+
ResponseEntity<String> entity = rest.getForEntity("/actuator/war", String.class);
85+
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
86+
}
87+
8288
}

spring-boot-tests/spring-boot-integration-tests/spring-boot-server-tests/src/test/java/org/springframework/boot/context/embedded/EmbeddedServletContainerWarPackagingIntegrationTests.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ void loaderClassesAreNotAvailableViaResourcePaths(RestTemplate rest) {
102102
.noneMatch((resourcePath) -> resourcePath.startsWith("/org/springframework/boot/loader"));
103103
}
104104

105+
@TestTemplate
106+
void conditionalOnWarDeploymentBeanIsNotAvailableForEmbeddedServer(RestTemplate rest) {
107+
ResponseEntity<String> entity = rest.getForEntity("/actuator/war", String.class);
108+
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
109+
}
110+
105111
private List<String> readLines(String input) {
106112
if (input == null) {
107113
return Collections.emptyList();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2+
com.autoconfig.ExampleAutoConfiguration
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
management.endpoints.web.exposure.include: '*'

0 commit comments

Comments
 (0)