Skip to content

Commit

Permalink
Introduce NegatableSpringBootCondition for negating condition
Browse files Browse the repository at this point in the history
  • Loading branch information
quaff committed Feb 20, 2024
1 parent 4fd0e29 commit 0b3ee74
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -87,6 +87,7 @@
* @author Maciej Walkowiak
* @author Stephane Nicoll
* @author Phillip Webb
* @author Yanming Zhou
* @since 1.1.0
*/
@Retention(RetentionPolicy.RUNTIME)
Expand Down Expand Up @@ -135,4 +136,10 @@
*/
boolean matchIfMissing() default false;

/**
* Specify if the condition should be negating. Defaults to {@code false}.
* @return if the condition should be negating
*/
boolean negating() default false;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.autoconfigure.condition;

import java.lang.annotation.Annotation;
import java.util.Map;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;

/**
* Base of all negatable {@link Condition} implementations used with Spring Boot.
*
* @author Yanming Zhou
*/
public abstract class NegatableSpringBootCondition<T extends Annotation> extends SpringBootCondition {

public static final String NEGATING_ATTRIBUTE_NAME = "negating";

@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
boolean result = super.matches(context, metadata);
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(getAnnotationName());
if (annotationAttributes != null && annotationAttributes.containsKey(NEGATING_ATTRIBUTE_NAME)) {
if ((Boolean) annotationAttributes.get(NEGATING_ATTRIBUTE_NAME)) {
result = !result;
}
}
return result;
}

protected String getAnnotationName() {
Class<?> clazz = ResolvableType.forClass(getClass())
.as(NegatableSpringBootCondition.class)
.getGeneric()
.resolve();
Assert.state(clazz != null, "Type argument of NegatableSpringBootCondition should be present");
return clazz.getName();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@
* @author Phillip Webb
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Yanming Zhou
* @see ConditionalOnProperty
*/
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {
class OnPropertyCondition extends NegatableSpringBootCondition<ConditionalOnProperty> {

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public abstract class SpringBootCondition implements Condition {
private final Log logger = LogFactory.getLog(getClass());

@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome outcome = getMatchOutcome(context, metadata);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -45,6 +45,7 @@
* @author Stephane Nicoll
* @author Phillip Webb
* @author Andy Wilkinson
* @author Yanming Zhou
*/
class ConditionalOnPropertyTests {

Expand Down Expand Up @@ -271,6 +272,30 @@ void metaAndDirectAnnotationWithAliasConditionMatchesWhenBothPropertiesAreSet()
assertThat(this.context.containsBean("foo")).isTrue();
}

@Test
void conditionDoesMatchWithPropertyNotDefined() {
load(NegatingConfiguration.class);
assertThat(this.context.containsBean("foo")).isTrue();
}

@Test
void conditionDoesNotMatchWithPropertyDefined() {
load(NegatingConfiguration.class, "property=foo");
assertThat(this.context.containsBean("foo")).isFalse();
}

@Test
void conditionDoesMatchWithPropertyNotHavingValue() {
load(NegatingConfiguration.class, "property=foo");
assertThat(this.context.containsBean("bar")).isTrue();
}

@Test
void conditionDoesNotMatchWithPropertyHavingValue() {
load(NegatingConfiguration.class, "property=bar");
assertThat(this.context.containsBean("bar")).isFalse();
}

private void load(Class<?> config, String... environment) {
TestPropertyValues.of(environment).applyTo(this.environment);
this.context = new SpringApplicationBuilder(config).environment(this.environment)
Expand All @@ -289,6 +314,23 @@ String foo() {

}

@Configuration(proxyBeanMethods = false)
static class NegatingConfiguration {

@Bean
@ConditionalOnProperty(name = "property", negating = true)
String foo() {
return "foo";
}

@Bean
@ConditionalOnProperty(name = "property", havingValue = "bar", negating = true)
String bar() {
return "bar";
}

}

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.", name = "the-relaxed-property")
static class RelaxedPropertiesRequiredConfiguration {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.autoconfigure.condition;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import org.junit.jupiter.api.Test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.AnnotatedTypeMetadata;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests for {@link NegatableSpringBootCondition}.
*
* @author Yanming Zhou
*/
class NegatableSpringBootConditionTests {

@Test
void notNegating() {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
assertThat(context.containsBean("foo")).isTrue();
}

@Test
void negating() {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
assertThat(context.containsBean("bar")).isFalse();
}

@Configuration(proxyBeanMethods = false)
static class Config {

@Bean
@AlwaysTrueConditional
String foo() {
return "bean";
}

@Bean
@AlwaysTrueConditional(negating = true)
String bar() {
return "bean";
}

}

@Retention(RetentionPolicy.RUNTIME)
@Conditional(AlwaysTrueCondition.class)
public @interface AlwaysTrueConditional {

boolean negating() default false;

}

static class AlwaysTrueCondition extends NegatableSpringBootCondition<AlwaysTrueConditional> {

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
return ConditionOutcome.match();
}

}

}

0 comments on commit 0b3ee74

Please sign in to comment.