Browse Source
Provide a way for auto-configuration tests to easily filter scanned components. See gh-4901pull/5488/head
9 changed files with 755 additions and 0 deletions
@ -0,0 +1,110 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2016 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 |
||||||
|
* |
||||||
|
* http://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.test.autoconfigure.filter; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.lang.annotation.Annotation; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanClassLoaderAware; |
||||||
|
import org.springframework.boot.context.TypeExcludeFilter; |
||||||
|
import org.springframework.context.annotation.ComponentScan.Filter; |
||||||
|
import org.springframework.core.type.classreading.MetadataReader; |
||||||
|
import org.springframework.core.type.classreading.MetadataReaderFactory; |
||||||
|
import org.springframework.core.type.filter.AnnotationTypeFilter; |
||||||
|
import org.springframework.core.type.filter.AssignableTypeFilter; |
||||||
|
|
||||||
|
/** |
||||||
|
* Abstract base class for a {@link TypeExcludeFilter} that can be customized using an |
||||||
|
* annotation. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
* @since 1.4.0 |
||||||
|
*/ |
||||||
|
public abstract class AnnotationCustomizableTypeExcludeFilter extends TypeExcludeFilter |
||||||
|
implements BeanClassLoaderAware { |
||||||
|
|
||||||
|
private ClassLoader classLoader; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setBeanClassLoader(ClassLoader classLoader) { |
||||||
|
this.classLoader = classLoader; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean match(MetadataReader metadataReader, |
||||||
|
MetadataReaderFactory metadataReaderFactory) throws IOException { |
||||||
|
if (hasAnnotation()) { |
||||||
|
return !(include(metadataReader, metadataReaderFactory) |
||||||
|
&& !exclude(metadataReader, metadataReaderFactory)); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
protected boolean include(MetadataReader metadataReader, |
||||||
|
MetadataReaderFactory metadataReaderFactory) throws IOException { |
||||||
|
if (new FilterAnnotations(this.classLoader, getFilters(FilterType.INCLUDE)) |
||||||
|
.anyMatches(metadataReader, metadataReaderFactory)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (isUseDefaultFilters() |
||||||
|
&& defaultInclude(metadataReader, metadataReaderFactory)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
protected boolean defaultInclude(MetadataReader metadataReader, |
||||||
|
MetadataReaderFactory metadataReaderFactory) throws IOException { |
||||||
|
for (Class<?> include : getDefaultIncudes()) { |
||||||
|
if (isTypeOrAnnotated(metadataReader, metadataReaderFactory, include)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
protected boolean exclude(MetadataReader metadataReader, |
||||||
|
MetadataReaderFactory metadataReaderFactory) throws IOException { |
||||||
|
return new FilterAnnotations(this.classLoader, getFilters(FilterType.EXCLUDE)) |
||||||
|
.anyMatches(metadataReader, metadataReaderFactory); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
protected final boolean isTypeOrAnnotated(MetadataReader metadataReader, |
||||||
|
MetadataReaderFactory metadataReaderFactory, Class<?> type) |
||||||
|
throws IOException { |
||||||
|
AnnotationTypeFilter annotationFilter = new AnnotationTypeFilter( |
||||||
|
(Class<? extends Annotation>) type); |
||||||
|
AssignableTypeFilter typeFilter = new AssignableTypeFilter(type); |
||||||
|
return annotationFilter.match(metadataReader, metadataReaderFactory) |
||||||
|
|| typeFilter.match(metadataReader, metadataReaderFactory); |
||||||
|
} |
||||||
|
|
||||||
|
protected abstract boolean hasAnnotation(); |
||||||
|
|
||||||
|
protected abstract Filter[] getFilters(FilterType type); |
||||||
|
|
||||||
|
protected abstract boolean isUseDefaultFilters(); |
||||||
|
|
||||||
|
protected abstract Set<Class<?>> getDefaultIncudes(); |
||||||
|
|
||||||
|
protected static enum FilterType { |
||||||
|
INCLUDE, EXCLUDE |
||||||
|
}; |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,114 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2016 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 |
||||||
|
* |
||||||
|
* http://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.test.autoconfigure.filter; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.lang.annotation.Annotation; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
import org.springframework.beans.BeanUtils; |
||||||
|
import org.springframework.context.annotation.ComponentScan.Filter; |
||||||
|
import org.springframework.context.annotation.FilterType; |
||||||
|
import org.springframework.core.type.classreading.MetadataReader; |
||||||
|
import org.springframework.core.type.classreading.MetadataReaderFactory; |
||||||
|
import org.springframework.core.type.filter.AnnotationTypeFilter; |
||||||
|
import org.springframework.core.type.filter.AspectJTypeFilter; |
||||||
|
import org.springframework.core.type.filter.AssignableTypeFilter; |
||||||
|
import org.springframework.core.type.filter.RegexPatternTypeFilter; |
||||||
|
import org.springframework.core.type.filter.TypeFilter; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* Utility to load {@link TypeFilter TypeFilters} from {@link Filter @Filter} annotations. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
* @since 1.4.0 |
||||||
|
*/ |
||||||
|
public class FilterAnnotations implements Iterable<TypeFilter> { |
||||||
|
|
||||||
|
private final ClassLoader classLoader; |
||||||
|
|
||||||
|
private final List<TypeFilter> filters; |
||||||
|
|
||||||
|
public FilterAnnotations(ClassLoader classLoader, Filter[] filters) { |
||||||
|
Assert.notNull(filters, "Filters must not be null"); |
||||||
|
this.classLoader = classLoader; |
||||||
|
this.filters = createTypeFilters(filters); |
||||||
|
} |
||||||
|
|
||||||
|
private List<TypeFilter> createTypeFilters(Filter[] filters) { |
||||||
|
List<TypeFilter> typeFilters = new ArrayList<TypeFilter>(); |
||||||
|
for (Filter filter : filters) { |
||||||
|
for (Class<?> filterClass : filter.classes()) { |
||||||
|
typeFilters.add(createTypeFilter(filter.type(), filterClass)); |
||||||
|
} |
||||||
|
for (String pattern : filter.pattern()) { |
||||||
|
typeFilters.add(createTypeFilter(filter.type(), pattern)); |
||||||
|
} |
||||||
|
} |
||||||
|
return Collections.unmodifiableList(typeFilters); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
private TypeFilter createTypeFilter(FilterType filterType, Class<?> filterClass) { |
||||||
|
switch (filterType) { |
||||||
|
case ANNOTATION: |
||||||
|
Assert.isAssignable(Annotation.class, filterClass, |
||||||
|
"An error occured while processing a ANNOTATION type filter: "); |
||||||
|
return new AnnotationTypeFilter((Class<Annotation>) filterClass); |
||||||
|
case ASSIGNABLE_TYPE: |
||||||
|
return new AssignableTypeFilter(filterClass); |
||||||
|
case CUSTOM: |
||||||
|
Assert.isAssignable(TypeFilter.class, filterClass, |
||||||
|
"An error occured while processing a CUSTOM type filter: "); |
||||||
|
return BeanUtils.instantiateClass(filterClass, TypeFilter.class); |
||||||
|
} |
||||||
|
throw new IllegalArgumentException( |
||||||
|
"Filter type not supported with Class value: " + filterType); |
||||||
|
} |
||||||
|
|
||||||
|
private TypeFilter createTypeFilter(FilterType filterType, String pattern) { |
||||||
|
switch (filterType) { |
||||||
|
case ASPECTJ: |
||||||
|
return new AspectJTypeFilter(pattern, this.classLoader); |
||||||
|
case REGEX: |
||||||
|
return new RegexPatternTypeFilter(Pattern.compile(pattern)); |
||||||
|
} |
||||||
|
throw new IllegalArgumentException( |
||||||
|
"Filter type not supported with String pattern: " + filterType); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Iterator<TypeFilter> iterator() { |
||||||
|
return this.filters.iterator(); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean anyMatches(MetadataReader metadataReader, |
||||||
|
MetadataReaderFactory metadataReaderFactory) throws IOException { |
||||||
|
for (TypeFilter filter : this) { |
||||||
|
if (filter.match(metadataReader, metadataReaderFactory)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,51 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2016 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 |
||||||
|
* |
||||||
|
* http://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.test.autoconfigure.filter; |
||||||
|
|
||||||
|
import java.lang.annotation.Documented; |
||||||
|
import java.lang.annotation.ElementType; |
||||||
|
import java.lang.annotation.Retention; |
||||||
|
import java.lang.annotation.RetentionPolicy; |
||||||
|
import java.lang.annotation.Target; |
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||||
|
import org.springframework.boot.context.TypeExcludeFilter; |
||||||
|
|
||||||
|
/** |
||||||
|
* Annotation that can on tests to define a set of {@link TypeExcludeFilter } classes that |
||||||
|
* should be applied to {@link SpringBootApplication @SpringBootApplication} component |
||||||
|
* scanning. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
* @since 1.4.0 |
||||||
|
* @see TypeExcludeFilter |
||||||
|
*/ |
||||||
|
@Target(ElementType.TYPE) |
||||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||||
|
@Documented |
||||||
|
public @interface TypeExcludeFilters { |
||||||
|
|
||||||
|
/** |
||||||
|
* Specifies {@link TypeExcludeFilter} classes that should be applied to |
||||||
|
* {@link SpringBootApplication @SpringBootApplication} component scanning. Classes |
||||||
|
* specified here can either have a no-arg constructor or accept a single |
||||||
|
* {@code Class<?>} argument if then need access to the {@code testClass}. |
||||||
|
* @see TypeExcludeFilter |
||||||
|
*/ |
||||||
|
Class<? extends TypeExcludeFilter>[] value(); |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,119 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2016 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 |
||||||
|
* |
||||||
|
* http://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.test.autoconfigure.filter; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.lang.reflect.Constructor; |
||||||
|
import java.util.LinkedHashSet; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import org.springframework.boot.context.TypeExcludeFilter; |
||||||
|
import org.springframework.context.ConfigurableApplicationContext; |
||||||
|
import org.springframework.core.type.classreading.MetadataReader; |
||||||
|
import org.springframework.core.type.classreading.MetadataReaderFactory; |
||||||
|
import org.springframework.test.context.ContextCustomizer; |
||||||
|
import org.springframework.test.context.MergedContextConfiguration; |
||||||
|
import org.springframework.util.ReflectionUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link ContextCustomizer} to support {@link TypeExcludeFilters @TypeExcludeFilters}. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
* @see TypeExcludeFilters |
||||||
|
*/ |
||||||
|
class TypeExcludeFiltersContextCustomizer implements ContextCustomizer { |
||||||
|
|
||||||
|
private static final String EXCLUDE_FILTER_BEAN_NAME = TypeExcludeFilters.class |
||||||
|
.getName(); |
||||||
|
|
||||||
|
private final Class<?> testClass; |
||||||
|
|
||||||
|
private final Set<Class<? extends TypeExcludeFilter>> filterClasses; |
||||||
|
|
||||||
|
TypeExcludeFiltersContextCustomizer(Class<?> testClass, |
||||||
|
Set<Class<? extends TypeExcludeFilter>> filterClasses) { |
||||||
|
this.testClass = testClass; |
||||||
|
this.filterClasses = filterClasses; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
return this.filterClasses.hashCode(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object obj) { |
||||||
|
return (obj != null && getClass().equals(obj.getClass()) && this.filterClasses |
||||||
|
.equals(((TypeExcludeFiltersContextCustomizer) obj).filterClasses)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void customizeContext(ConfigurableApplicationContext context, |
||||||
|
MergedContextConfiguration mergedContextConfiguration) { |
||||||
|
if (!this.filterClasses.isEmpty()) { |
||||||
|
context.getBeanFactory().registerSingleton(EXCLUDE_FILTER_BEAN_NAME, |
||||||
|
createDelegatingTypeExcludeFilter()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private TypeExcludeFilter createDelegatingTypeExcludeFilter() { |
||||||
|
final Set<TypeExcludeFilter> filters = new LinkedHashSet<TypeExcludeFilter>( |
||||||
|
this.filterClasses.size()); |
||||||
|
for (Class<? extends TypeExcludeFilter> filterClass : this.filterClasses) { |
||||||
|
filters.add(createTypeExcludeFilter(filterClass)); |
||||||
|
} |
||||||
|
return new TypeExcludeFilter() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean match(MetadataReader metadataReader, |
||||||
|
MetadataReaderFactory metadataReaderFactory) throws IOException { |
||||||
|
for (TypeExcludeFilter filter : filters) { |
||||||
|
if (filter.match(metadataReader, metadataReaderFactory)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
private TypeExcludeFilter createTypeExcludeFilter(Class<?> type) { |
||||||
|
try { |
||||||
|
Constructor<?> constructor = getTypeExcludeFilterConstructor(type); |
||||||
|
ReflectionUtils.makeAccessible(constructor); |
||||||
|
if (constructor.getParameterTypes().length == 1) { |
||||||
|
return (TypeExcludeFilter) constructor.newInstance(this.testClass); |
||||||
|
} |
||||||
|
return (TypeExcludeFilter) constructor.newInstance(); |
||||||
|
} |
||||||
|
catch (Exception ex) { |
||||||
|
throw new IllegalStateException("Unable to create filter for " + type, ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Constructor<?> getTypeExcludeFilterConstructor(Class<?> type) |
||||||
|
throws NoSuchMethodException { |
||||||
|
try { |
||||||
|
return type.getDeclaredConstructor(Class.class); |
||||||
|
} |
||||||
|
catch (Exception ex) { |
||||||
|
return type.getDeclaredConstructor(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,52 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2016 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 |
||||||
|
* |
||||||
|
* http://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.test.autoconfigure.filter; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.LinkedHashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import org.springframework.boot.context.TypeExcludeFilter; |
||||||
|
import org.springframework.core.annotation.AnnotatedElementUtils; |
||||||
|
import org.springframework.test.context.ContextConfigurationAttributes; |
||||||
|
import org.springframework.test.context.ContextCustomizer; |
||||||
|
import org.springframework.test.context.ContextCustomizerFactory; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link ContextCustomizerFactory} to support |
||||||
|
* {@link TypeExcludeFilters @TypeExcludeFilters}. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
* @see TypeExcludeFiltersContextCustomizer |
||||||
|
*/ |
||||||
|
class TypeExcludeFiltersContextCustomizerFactory implements ContextCustomizerFactory { |
||||||
|
|
||||||
|
@Override |
||||||
|
public ContextCustomizer createContextCustomizer(Class<?> testClass, |
||||||
|
List<ContextConfigurationAttributes> configurationAttributes) { |
||||||
|
TypeExcludeFilters annotation = AnnotatedElementUtils |
||||||
|
.findMergedAnnotation(testClass, TypeExcludeFilters.class); |
||||||
|
if (annotation != null) { |
||||||
|
Set<Class<? extends TypeExcludeFilter>> filterClasses = new LinkedHashSet<Class<? extends TypeExcludeFilter>>( |
||||||
|
Arrays.asList(annotation.value())); |
||||||
|
return new TypeExcludeFiltersContextCustomizer(testClass, filterClasses); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2016 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 |
||||||
|
* |
||||||
|
* http://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. |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* Helper utilities for using {@link org.springframework.boot.context.TypeExcludeFilter} |
||||||
|
* with auto-configured tests. |
||||||
|
*/ |
||||||
|
package org.springframework.boot.test.autoconfigure.filter; |
||||||
@ -0,0 +1,152 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2016 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 |
||||||
|
* |
||||||
|
* http://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.test.autoconfigure.filter; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.lang.annotation.Documented; |
||||||
|
import java.lang.annotation.ElementType; |
||||||
|
import java.lang.annotation.Retention; |
||||||
|
import java.lang.annotation.RetentionPolicy; |
||||||
|
import java.lang.annotation.Target; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import org.springframework.context.annotation.ComponentScan.Filter; |
||||||
|
import org.springframework.context.annotation.FilterType; |
||||||
|
import org.springframework.core.annotation.AnnotatedElementUtils; |
||||||
|
import org.springframework.core.type.classreading.MetadataReader; |
||||||
|
import org.springframework.core.type.classreading.MetadataReaderFactory; |
||||||
|
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; |
||||||
|
import org.springframework.core.type.filter.TypeFilter; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link FilterAnnotations}. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
*/ |
||||||
|
public class FilterAnnotationsTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void filterAnnotation() throws Exception { |
||||||
|
FilterAnnotations filterAnnotations = get(FilterByAnnotation.class); |
||||||
|
assertThat(match(filterAnnotations, ExampleWithAnnotation.class)).isTrue(); |
||||||
|
assertThat(match(filterAnnotations, ExampleWithoutAnnotation.class)).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void filterAssignableType() throws Exception { |
||||||
|
FilterAnnotations filterAnnotations = get(FilterByType.class); |
||||||
|
assertThat(match(filterAnnotations, ExampleWithAnnotation.class)).isFalse(); |
||||||
|
assertThat(match(filterAnnotations, ExampleWithoutAnnotation.class)).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void filterCustom() throws Exception { |
||||||
|
FilterAnnotations filterAnnotations = get(FilterByCustom.class); |
||||||
|
assertThat(match(filterAnnotations, ExampleWithAnnotation.class)).isFalse(); |
||||||
|
assertThat(match(filterAnnotations, ExampleWithoutAnnotation.class)).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void filterAspectJ() throws Exception { |
||||||
|
FilterAnnotations filterAnnotations = get(FilterByAspectJ.class); |
||||||
|
assertThat(match(filterAnnotations, ExampleWithAnnotation.class)).isFalse(); |
||||||
|
assertThat(match(filterAnnotations, ExampleWithoutAnnotation.class)).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void filterRegex() throws Exception { |
||||||
|
FilterAnnotations filterAnnotations = get(FilterByRegex.class); |
||||||
|
assertThat(match(filterAnnotations, ExampleWithAnnotation.class)).isFalse(); |
||||||
|
assertThat(match(filterAnnotations, ExampleWithoutAnnotation.class)).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void anyMatches() throws Exception { |
||||||
|
} |
||||||
|
|
||||||
|
private FilterAnnotations get(Class<?> type) { |
||||||
|
Filters filters = AnnotatedElementUtils.getMergedAnnotation(type, Filters.class); |
||||||
|
return new FilterAnnotations(getClass().getClassLoader(), filters.value()); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean match(FilterAnnotations filterAnnotations, Class<?> type) |
||||||
|
throws IOException { |
||||||
|
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); |
||||||
|
MetadataReader metadataReader = metadataReaderFactory |
||||||
|
.getMetadataReader(type.getName()); |
||||||
|
return filterAnnotations.anyMatches(metadataReader, metadataReaderFactory); |
||||||
|
} |
||||||
|
|
||||||
|
@Filters(@Filter(Service.class)) |
||||||
|
static class FilterByAnnotation { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Filters(@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ExampleWithoutAnnotation.class)) |
||||||
|
static class FilterByType { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Filters(@Filter(type = FilterType.CUSTOM, classes = ExampleCustomFilter.class)) |
||||||
|
static class FilterByCustom { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Filters(@Filter(type = FilterType.ASPECTJ, pattern = "(*..*ExampleWithoutAnnotation)")) |
||||||
|
static class FilterByAspectJ { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Filters(@Filter(type = FilterType.REGEX, pattern = ".*ExampleWithoutAnnotation")) |
||||||
|
static class FilterByRegex { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Target({ ElementType.TYPE }) |
||||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||||
|
@Documented |
||||||
|
static @interface Filters { |
||||||
|
|
||||||
|
Filter[] value(); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
static class ExampleCustomFilter implements TypeFilter { |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean match(MetadataReader metadataReader, |
||||||
|
MetadataReaderFactory metadataReaderFactory) throws IOException { |
||||||
|
return metadataReader.getClassMetadata().getClassName() |
||||||
|
.equals(ExampleWithoutAnnotation.class.getName()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Service |
||||||
|
static class ExampleWithAnnotation { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
static class ExampleWithoutAnnotation { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,135 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2016 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 |
||||||
|
* |
||||||
|
* http://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.test.autoconfigure.filter; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import org.springframework.boot.context.TypeExcludeFilter; |
||||||
|
import org.springframework.context.ConfigurableApplicationContext; |
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||||
|
import org.springframework.core.type.classreading.MetadataReader; |
||||||
|
import org.springframework.core.type.classreading.MetadataReaderFactory; |
||||||
|
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; |
||||||
|
import org.springframework.test.context.ContextCustomizer; |
||||||
|
import org.springframework.test.context.MergedContextConfiguration; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link TypeExcludeFiltersContextCustomizerFactory}. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
*/ |
||||||
|
public class TypeExcludeFiltersTestContextCustomizerFactoryTests { |
||||||
|
|
||||||
|
private TypeExcludeFiltersContextCustomizerFactory factory = new TypeExcludeFiltersContextCustomizerFactory(); |
||||||
|
|
||||||
|
private MergedContextConfiguration mergedContextConfiguration = mock( |
||||||
|
MergedContextConfiguration.class); |
||||||
|
|
||||||
|
private ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getContextCustomizerWhenHasNoAnnotationShouldReturnNull() |
||||||
|
throws Exception { |
||||||
|
ContextCustomizer customizer = this.factory |
||||||
|
.createContextCustomizer(NoAnnotation.class, null); |
||||||
|
assertThat(customizer).isNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getContextCustomizerWhenHasAnnotationShouldReturnCustomizer() |
||||||
|
throws Exception { |
||||||
|
ContextCustomizer customizer = this.factory |
||||||
|
.createContextCustomizer(WithExcludeFilters.class, null); |
||||||
|
assertThat(customizer).isNotNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void hashCodeAndEquals() throws Exception { |
||||||
|
ContextCustomizer customizer1 = this.factory |
||||||
|
.createContextCustomizer(WithExcludeFilters.class, null); |
||||||
|
ContextCustomizer customizer2 = this.factory |
||||||
|
.createContextCustomizer(WithSameExcludeFilters.class, null); |
||||||
|
ContextCustomizer customizer3 = this.factory |
||||||
|
.createContextCustomizer(WithDifferentExcludeFilters.class, null); |
||||||
|
assertThat(customizer1.hashCode()).isEqualTo(customizer2.hashCode()); |
||||||
|
assertThat(customizer1).isEqualTo(customizer1).isEqualTo(customizer2) |
||||||
|
.isNotEqualTo(customizer3); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getContextCustomizerShouldAddExcludeFilters() throws Exception { |
||||||
|
ContextCustomizer customizer = this.factory |
||||||
|
.createContextCustomizer(WithExcludeFilters.class, null); |
||||||
|
customizer.customizeContext(this.context, this.mergedContextConfiguration); |
||||||
|
this.context.refresh(); |
||||||
|
TypeExcludeFilter filter = this.context.getBean(TypeExcludeFilter.class); |
||||||
|
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); |
||||||
|
MetadataReader metadataReader = metadataReaderFactory |
||||||
|
.getMetadataReader(NoAnnotation.class.getName()); |
||||||
|
assertThat(filter.match(metadataReader, metadataReaderFactory)).isFalse(); |
||||||
|
metadataReader = metadataReaderFactory |
||||||
|
.getMetadataReader(SimpleExclude.class.getName()); |
||||||
|
assertThat(filter.match(metadataReader, metadataReaderFactory)).isTrue(); |
||||||
|
metadataReader = metadataReaderFactory |
||||||
|
.getMetadataReader(TestClassAwareExclude.class.getName()); |
||||||
|
assertThat(filter.match(metadataReader, metadataReaderFactory)).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
static class NoAnnotation { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@TypeExcludeFilters({ SimpleExclude.class, TestClassAwareExclude.class }) |
||||||
|
static class WithExcludeFilters { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@TypeExcludeFilters({ TestClassAwareExclude.class, SimpleExclude.class }) |
||||||
|
static class WithSameExcludeFilters { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@TypeExcludeFilters(SimpleExclude.class) |
||||||
|
static class WithDifferentExcludeFilters { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
static class SimpleExclude extends TypeExcludeFilter { |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean match(MetadataReader metadataReader, |
||||||
|
MetadataReaderFactory metadataReaderFactory) throws IOException { |
||||||
|
return metadataReader.getClassMetadata().getClassName() |
||||||
|
.equals(getClass().getName()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
static class TestClassAwareExclude extends SimpleExclude { |
||||||
|
|
||||||
|
TestClassAwareExclude(Class<?> testClass) { |
||||||
|
assertThat(testClass).isEqualTo(WithExcludeFilters.class); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue