/* * Copyright 2002-2014 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 com.freetmp.common.provider; import com.freetmp.common.resource.*; import com.freetmp.common.type.AnnotationMetadata; import com.freetmp.common.type.classreading.CachingMetadataReaderFactory; import com.freetmp.common.type.classreading.MetadataReader; import com.freetmp.common.type.classreading.MetadataReaderFactory; import com.freetmp.common.type.filter.TypeFilter; import com.freetmp.common.util.Assert; import com.freetmp.common.util.ClassUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; /* * A component provider that scans the classpath from a base package. It then * applies exclude and include filters to the resulting classes to find candidates. * <p/> * <p>This implementation is based on Spring's * {@link com.freetmp.common.type.classreading.MetadataReader MetadataReader} * facility, backed by an ASM {@link com.freetmp.common.asm.ClassReader ClassReader}. * * @author Mark Fisher * @author Juergen Hoeller * @author Ramnivas Laddad * @author Chris Beams * @see com.freetmp.common.type.classreading.MetadataReaderFactory * @see com.freetmp.common.type.AnnotationMetadata * @since 2.5 */ public class ClassPathScanningProvider { static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; protected final Logger logger = LoggerFactory.getLogger(getClass()); private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver); private String resourcePattern = DEFAULT_RESOURCE_PATTERN; private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>(); private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>(); public ClassPathScanningProvider() { } public void setResourceLoader(ResourceLoader resourceLoader) { this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); } /* * Return the ResourceLoader that this component provider uses. */ public final ResourceLoader getResourceLoader() { return this.resourcePatternResolver; } /* * Set the {@link MetadataReaderFactory} to use. * <p>Default is a {@link CachingMetadataReaderFactory} for the specified * {@linkplain #setResourceLoader resource loader}. * <p>Call this setter method <i>after</i> {@link #setResourceLoader} in order * for the given MetadataReaderFactory to override the default factory. */ public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) { this.metadataReaderFactory = metadataReaderFactory; } /* * Return the MetadataReaderFactory used by this component provider. */ public final MetadataReaderFactory getMetadataReaderFactory() { return this.metadataReaderFactory; } /* * Set the resource pattern to use when scanning the classpath. * This value will be appended to each base package name. * * @see #findCandidateComponents(String) * @see #DEFAULT_RESOURCE_PATTERN */ public void setResourcePattern(String resourcePattern) { Assert.notNull(resourcePattern, "'resourcePattern' must not be null"); this.resourcePattern = resourcePattern; } /* * Add an include type filter to the <i>end</i> of the inclusion list. */ public void addIncludeFilter(TypeFilter includeFilter) { this.includeFilters.add(includeFilter); } /* * Add an exclude type filter to the <i>front</i> of the exclusion list. */ public void addExcludeFilter(TypeFilter excludeFilter) { this.excludeFilters.add(0, excludeFilter); } public void resetFilters() { this.includeFilters.clear(); this.excludeFilters.clear(); } /* * Scan the class path for candidate components. * * @param basePackage the package to check for annotated classes * @return a corresponding Set of autodetected bean definitions */ public Set<MetadataReader> findCandidateComponents(String basePackage) throws IOException { Set<MetadataReader> candidates = new LinkedHashSet<>(); String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + "/" + this.resourcePattern; Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); for (Resource resource : resources) { logger.trace("Scanning " + resource); if (resource.isReadable()) { try { MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); if (isCandidateComponent(metadataReader)) { candidates.add(metadataReader); } } catch (IOException e) { logger.warn("Error reading " + resource, e); } } else { logger.trace("Ignored because not readable: " + resource); } } return candidates; } /* * Resolve the specified base package into a pattern specification for * the package search path. * <p>The default implementation resolves placeholders against system properties, * and converts a "."-based package path to a "/"-based resource path. * * @param basePackage the base package as specified by the user * @return the pattern specification to be used for package searching */ protected String resolveBasePackage(String basePackage) { return ClassUtils.convertClassNameToResourcePath(basePackage); } /* * Determine whether the given class does not match any exclude filter * and does match at least one include filter. * * @param metadataReader the ASM ClassReader for the class * @return whether the class qualifies as a candidate component */ protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return true; } } return false; } /* * Determine whether the given bean definition qualifies as candidate. * <p>The default implementation checks whether the class is concrete * (i.e. not abstract and not an interface). Can be overridden in subclasses. * * @param metadata the bean metadata to check * @return whether the bean definition qualifies as a candidate component */ protected boolean isCandidateComponent(AnnotationMetadata metadata) { return (metadata.isConcrete() && metadata.isIndependent()); } /* * Clear the underlying metadata cache, removing all cached class metadata. */ public void clearCache() { if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } } }