Improve handling of annotation properties in architecture checks
Closes gh-47437
This commit is contained in:
		
							parent
							
								
									61acc52137
								
							
						
					
					
						commit
						3ba61c3fa7
					
				|  | @ -89,7 +89,7 @@ final class ArchitectureRules { | ||||||
| 		rules.add(noClassesShouldCallStringToUpperCaseWithoutLocale()); | 		rules.add(noClassesShouldCallStringToUpperCaseWithoutLocale()); | ||||||
| 		rules.add(noClassesShouldCallStringToLowerCaseWithoutLocale()); | 		rules.add(noClassesShouldCallStringToLowerCaseWithoutLocale()); | ||||||
| 		rules.add(conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType()); | 		rules.add(conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType()); | ||||||
| 		rules.add(enumSourceShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodParameterType()); | 		rules.add(enumSourceShouldNotHaveValueThatIsTheSameAsTypeOfMethodsFirstParameter()); | ||||||
| 		rules.add(allConfigurationPropertiesBindingBeanMethodsShouldBeStatic()); | 		rules.add(allConfigurationPropertiesBindingBeanMethodsShouldBeStatic()); | ||||||
| 		return List.copyOf(rules); | 		return List.copyOf(rules); | ||||||
| 	} | 	} | ||||||
|  | @ -222,7 +222,7 @@ final class ArchitectureRules { | ||||||
| 			JavaAnnotation<JavaMethod> conditionalAnnotation = item | 			JavaAnnotation<JavaMethod> conditionalAnnotation = item | ||||||
| 				.getAnnotationOfType("org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean"); | 				.getAnnotationOfType("org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean"); | ||||||
| 			Map<String, Object> properties = conditionalAnnotation.getProperties(); | 			Map<String, Object> properties = conditionalAnnotation.getProperties(); | ||||||
| 			if (!properties.containsKey("type") && !properties.containsKey("name")) { | 			if (!hasProperty("type", properties) && !hasProperty("name", properties)) { | ||||||
| 				conditionalAnnotation.get("value").ifPresent((value) -> { | 				conditionalAnnotation.get("value").ifPresent((value) -> { | ||||||
| 					if (containsOnlySingleType((JavaType[]) value, item.getReturnType())) { | 					if (containsOnlySingleType((JavaType[]) value, item.getReturnType())) { | ||||||
| 						addViolation(events, item, conditionalAnnotation.getDescription() | 						addViolation(events, item, conditionalAnnotation.getDescription() | ||||||
|  | @ -233,16 +233,24 @@ final class ArchitectureRules { | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	private static ArchRule enumSourceShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodParameterType() { | 	private static boolean hasProperty(String name, Map<String, Object> properties) { | ||||||
|  | 		Object property = properties.get(name); | ||||||
|  | 		if (property == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		return !property.getClass().isArray() || ((Object[]) property).length > 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private static ArchRule enumSourceShouldNotHaveValueThatIsTheSameAsTypeOfMethodsFirstParameter() { | ||||||
| 		return ArchRuleDefinition.methods() | 		return ArchRuleDefinition.methods() | ||||||
| 			.that() | 			.that() | ||||||
| 			.areAnnotatedWith("org.junit.jupiter.params.provider.EnumSource") | 			.areAnnotatedWith("org.junit.jupiter.params.provider.EnumSource") | ||||||
| 			.should(notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType()) | 			.should(notHaveValueThatIsTheSameAsTheTypeOfTheMethodsFirstParameter()) | ||||||
| 			.allowEmptyShould(true); | 			.allowEmptyShould(true); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	private static ArchCondition<? super JavaMethod> notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType() { | 	private static ArchCondition<? super JavaMethod> notHaveValueThatIsTheSameAsTheTypeOfTheMethodsFirstParameter() { | ||||||
| 		return check("not specify only a type that is the same as the method's parameter type", | 		return check("not have a value that is the same as the type of the method's first parameter", | ||||||
| 				ArchitectureRules::notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType); | 				ArchitectureRules::notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -250,16 +258,14 @@ final class ArchitectureRules { | ||||||
| 			ConditionEvents events) { | 			ConditionEvents events) { | ||||||
| 		JavaAnnotation<JavaMethod> enumSourceAnnotation = item | 		JavaAnnotation<JavaMethod> enumSourceAnnotation = item | ||||||
| 			.getAnnotationOfType("org.junit.jupiter.params.provider.EnumSource"); | 			.getAnnotationOfType("org.junit.jupiter.params.provider.EnumSource"); | ||||||
| 		Map<String, Object> properties = enumSourceAnnotation.getProperties(); |  | ||||||
| 		if (properties.size() == 1 && item.getParameterTypes().size() == 1) { |  | ||||||
| 		enumSourceAnnotation.get("value").ifPresent((value) -> { | 		enumSourceAnnotation.get("value").ifPresent((value) -> { | ||||||
| 				if (value.equals(item.getParameterTypes().get(0))) { | 			JavaType parameterType = item.getParameterTypes().get(0); | ||||||
|  | 			if (value.equals(parameterType)) { | ||||||
| 				addViolation(events, item, enumSourceAnnotation.getDescription() | 				addViolation(events, item, enumSourceAnnotation.getDescription() | ||||||
| 							+ " should not specify only a value that is the same as the method's parameter type"); | 						+ " should not specify a value that is the same as the type of the method's first parameter"); | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	private static ArchRule allConfigurationPropertiesBindingBeanMethodsShouldBeStatic() { | 	private static ArchRule allConfigurationPropertiesBindingBeanMethodsShouldBeStatic() { | ||||||
| 		return methodsThatAreAnnotatedWith("org.springframework.context.annotation.Bean").and() | 		return methodsThatAreAnnotatedWith("org.springframework.context.annotation.Bean").and() | ||||||
|  |  | ||||||
|  | @ -57,6 +57,8 @@ class ArchitectureCheckTests { | ||||||
| 
 | 
 | ||||||
| 	private static final String SPRING_CONTEXT = "org.springframework:spring-context:6.2.9"; | 	private static final String SPRING_CONTEXT = "org.springframework:spring-context:6.2.9"; | ||||||
| 
 | 
 | ||||||
|  | 	private static final String JUNIT_JUPITER = "org.junit.jupiter:junit-jupiter:5.12.0"; | ||||||
|  | 
 | ||||||
| 	private static final String SPRING_INTEGRATION_JMX = "org.springframework.integration:spring-integration-jmx:6.5.1"; | 	private static final String SPRING_INTEGRATION_JMX = "org.springframework.integration:spring-integration-jmx:6.5.1"; | ||||||
| 
 | 
 | ||||||
| 	private GradleBuild gradleBuild; | 	private GradleBuild gradleBuild; | ||||||
|  | @ -270,6 +272,29 @@ class ArchitectureCheckTests { | ||||||
| 		build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task); | 		build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void whenEnumSourceValueIsInferredShouldSucceedAndWriteEmptyReport() throws IOException { | ||||||
|  | 		prepareTask(Task.CHECK_ARCHITECTURE_TEST, "junit/enumsource/inferredfromparametertype"); | ||||||
|  | 		build(this.gradleBuild.withDependencies(JUNIT_JUPITER), Task.CHECK_ARCHITECTURE_TEST); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void whenEnumSourceValueIsNotTheSameAsTypeOfMethodsFirstParameterShouldSucceedAndWriteEmptyReport() | ||||||
|  | 			throws IOException { | ||||||
|  | 		prepareTask(Task.CHECK_ARCHITECTURE_TEST, "junit/enumsource/valuenecessary"); | ||||||
|  | 		build(this.gradleBuild.withDependencies(JUNIT_JUPITER), Task.CHECK_ARCHITECTURE_TEST); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void whenEnumSourceValueIsSameAsTypeOfMethodsFirstParameterShouldFailAndWriteReport() throws IOException { | ||||||
|  | 		prepareTask(Task.CHECK_ARCHITECTURE_TEST, "junit/enumsource/sameasparametertype"); | ||||||
|  | 		buildAndFail(this.gradleBuild.withDependencies(JUNIT_JUPITER), Task.CHECK_ARCHITECTURE_TEST, | ||||||
|  | 				"method <org.springframework.boot.build.architecture.junit.enumsource.sameasparametertype" | ||||||
|  | 						+ ".EnumSourceSameAsParameterType.exampleMethod(org.springframework.boot.build." | ||||||
|  | 						+ "architecture.junit.enumsource.sameasparametertype.EnumSourceSameAsParameterType$Example)>", | ||||||
|  | 				"should not have a value that is the same as the type of the method's first parameter"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	private void prepareTask(Task task, String... sourceDirectories) throws IOException { | 	private void prepareTask(Task task, String... sourceDirectories) throws IOException { | ||||||
| 		for (String sourceDirectory : sourceDirectories) { | 		for (String sourceDirectory : sourceDirectories) { | ||||||
| 			FileSystemUtils.copyRecursively( | 			FileSystemUtils.copyRecursively( | ||||||
|  |  | ||||||
|  | @ -0,0 +1,34 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright 2012-present 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.build.architecture.junit.enumsource.inferredfromparametertype; | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.params.provider.EnumSource; | ||||||
|  | 
 | ||||||
|  | class EnumSourceInferredFromParameterType { | ||||||
|  | 
 | ||||||
|  | 	@EnumSource | ||||||
|  | 	void exampleMethod(Example example) { | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	enum Example { | ||||||
|  | 
 | ||||||
|  | 		ONE, TWO, THREE | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,34 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright 2012-present 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.build.architecture.junit.enumsource.sameasparametertype; | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.params.provider.EnumSource; | ||||||
|  | 
 | ||||||
|  | class EnumSourceSameAsParameterType { | ||||||
|  | 
 | ||||||
|  | 	@EnumSource(Example.class) | ||||||
|  | 	void exampleMethod(Example example) { | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	enum Example { | ||||||
|  | 
 | ||||||
|  | 		ONE, TWO, THREE | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,34 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright 2012-present 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.build.architecture.junit.enumsource.valuenecessary; | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.params.provider.EnumSource; | ||||||
|  | 
 | ||||||
|  | class EnumSourceValueNecessary { | ||||||
|  | 
 | ||||||
|  | 	@EnumSource(Example.class) | ||||||
|  | 	void exampleMethod(String thing, Example example) { | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	enum Example { | ||||||
|  | 
 | ||||||
|  | 		ONE, TWO, THREE | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -112,7 +112,7 @@ class SbomEndpointWebExtensionTests { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@ParameterizedTest | 	@ParameterizedTest | ||||||
| 	@EnumSource(value = SbomType.class, names = "UNKNOWN", mode = Mode.EXCLUDE) | 	@EnumSource(names = "UNKNOWN", mode = Mode.EXCLUDE) | ||||||
| 	void shouldAutodetectFormats(SbomType type) throws IOException { | 	void shouldAutodetectFormats(SbomType type) throws IOException { | ||||||
| 		String content = getSbomContent(type); | 		String content = getSbomContent(type); | ||||||
| 		assertThat(type.matches(content)).isTrue(); | 		assertThat(type.matches(content)).isTrue(); | ||||||
|  |  | ||||||
|  | @ -45,8 +45,7 @@ class JacksonHttpMessageConvertersConfiguration { | ||||||
| 	static class MappingJackson2HttpMessageConverterConfiguration { | 	static class MappingJackson2HttpMessageConverterConfiguration { | ||||||
| 
 | 
 | ||||||
| 		@Bean | 		@Bean | ||||||
| 		@ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class, | 		@ConditionalOnMissingBean(ignoredType = { | ||||||
| 				ignoredType = { |  | ||||||
| 				"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", | 				"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", | ||||||
| 				"org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" }) | 				"org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" }) | ||||||
| 		MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) { | 		MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) { | ||||||
|  |  | ||||||
|  | @ -59,7 +59,7 @@ public class ErrorWebFluxAutoConfiguration { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@Bean | 	@Bean | ||||||
| 	@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT) | 	@ConditionalOnMissingBean(search = SearchStrategy.CURRENT) | ||||||
| 	@Order(-1) | 	@Order(-1) | ||||||
| 	public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes, | 	public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes, | ||||||
| 			WebProperties webProperties, ObjectProvider<ViewResolver> viewResolvers, | 			WebProperties webProperties, ObjectProvider<ViewResolver> viewResolvers, | ||||||
|  |  | ||||||
|  | @ -62,8 +62,8 @@ class BatchDataSourceScriptDatabaseInitializerTests { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@ParameterizedTest | 	@ParameterizedTest | ||||||
| 	@EnumSource(value = DatabaseDriver.class, mode = Mode.EXCLUDE, names = { "CLICKHOUSE", "FIREBIRD", "INFORMIX", | 	@EnumSource(mode = Mode.EXCLUDE, names = { "CLICKHOUSE", "FIREBIRD", "INFORMIX", "JTDS", "PHOENIX", "REDSHIFT", | ||||||
| 			"JTDS", "PHOENIX", "REDSHIFT", "TERADATA", "TESTCONTAINERS", "UNKNOWN" }) | 			"TERADATA", "TESTCONTAINERS", "UNKNOWN" }) | ||||||
| 	void batchSchemaCanBeLocated(DatabaseDriver driver) throws SQLException { | 	void batchSchemaCanBeLocated(DatabaseDriver driver) throws SQLException { | ||||||
| 		DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); | 		DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); | ||||||
| 		BatchProperties properties = new BatchProperties(); | 		BatchProperties properties = new BatchProperties(); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue