/* * Copyright 2002-20011 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.flex.core.io; import java.io.IOException; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; 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.RegexPatternTypeFilter; import org.springframework.core.type.filter.TypeFilter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.SystemPropertyUtils; /** * Generic extension of {@link AbstractAmfConversionServiceConfigProcessor} that configures the Spring {@link ConversionService}-based AMF * serialization/deserialization support via classpath scanning. * * <p> * This implementation will recursively scan for classes starting with the base package provided in the * {@link ClassPathScanningAmfConversionServiceConfigProcessor#ClassPathScanningAmfConversionServiceConfigProcessor(String) constructor}. * By default, a {@link SpringPropertyProxy} will be registered for every class found in the scan. * * <p> * The scanning process may be more finely tuned by providing {@link TypeFilter} implementations to be used in * {@link ClassPathScanningAmfConversionServiceConfigProcessor#setIncludeFilters(List) including} and * {@link ClassPathScanningAmfConversionServiceConfigProcessor#setExcludeFilters(List) excluding} specific classes. For example, you * can filter by {@link RegexPatternTypeFilter RegEx patterns}, {@link AnnotationTypeFilter custom annotations}, or anything else that * can be used for matching in a {@code TypeFilter}. * * <p> * This implementation does not register any additional {@link Converter Converters} beyond those registered in the parent class. * Additional {@code TypeConverters} may be registered by extending this class and overriding * {@link AbstractAmfConversionServiceConfigProcessor#configureConverters(org.springframework.core.convert.converter.ConverterRegistry) configureConverters}. * * <p> * The implementation is heavily derived from {@link ClassPathScanningCandidateComponentProvider}. * * @author Jeremy Grelle */ public class ClassPathScanningAmfConversionServiceConfigProcessor extends AbstractAmfConversionServiceConfigProcessor implements ResourceLoaderAware, BeanClassLoaderAware { private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver); private String resourcePattern = DEFAULT_RESOURCE_PATTERN; private ClassLoader beanClassLoader; private List<TypeFilter> includeFilters = new LinkedList<TypeFilter>(); private List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>(); private final String basePackage; /** * Create a ClassPathScanningAmfConversionServiceConfigProcessor * @param basePackage the base package to scan for classes to be registered for AMF serialization/deserialization */ public ClassPathScanningAmfConversionServiceConfigProcessor(String basePackage) { this.basePackage = basePackage; } /** * {@inheritDoc} */ @Override public void afterPropertiesSet() throws Exception { if (CollectionUtils.isEmpty(includeFilters)) { addIncludeFilter(new IncludeAllFilter()); } super.afterPropertiesSet(); } /** * {@inheritDoc} */ public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } /** * Set the ResourceLoader to use for resource locations. * This will typically be a ResourcePatternResolver implementation. * <p>Default is PathMatchingResourcePatternResolver, also capable of * resource pattern resolving through the ResourcePatternResolver interface. * @see org.springframework.core.io.support.ResourcePatternResolver * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver */ public void setResourceLoader(ResourceLoader resourceLoader) { this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); } /** * Return the ResourceLoader used in locating matching resources. */ public final ResourceLoader getResourceLoader() { return this.resourcePatternResolver; } /** * Set the resource pattern to use when scanning the classpath. * This value will be appended to each base package name. * @see #findTypesToRegister() * @see #DEFAULT_RESOURCE_PATTERN */ public void setResourcePattern(String resourcePattern) { Assert.notNull(resourcePattern, "'resourcePattern' must not be null"); this.resourcePattern = resourcePattern; } /** * Sets the list of type filters to use for inclusion. */ public void setIncludeFilters(List<TypeFilter> includeFilters) { this.includeFilters = includeFilters; } /** * Sets the list of type filters to use for exclusion. */ public void setExcludeFilters(List<TypeFilter> excludeFilters) { this.excludeFilters = excludeFilters; } /** * 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); } /** * {@inheritDoc} */ @Override protected Set<Class<?>> findTypesToRegister() { Set<Class<?>> typesToRegister = new HashSet<Class<?>>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + "/" + this.resourcePattern; Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); boolean traceEnabled = log.isTraceEnabled(); boolean debugEnabled = log.isDebugEnabled(); for (Resource resource : resources) { if (traceEnabled) { log.trace("Scanning " + resource); } if (resource.isReadable()) { try { MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); if (applyFilters(metadataReader)) { if (isCandidateForAmf(metadataReader.getAnnotationMetadata())) { if (debugEnabled) { log.debug("Identified candidate AMF class: " + resource); } typesToRegister.add(ClassUtils.forName(metadataReader.getAnnotationMetadata().getClassName(), this.beanClassLoader)); } else { if (debugEnabled) { log.debug("Ignored because not a concrete top-level class: " + resource); } } } else { if (traceEnabled) { log.trace("Ignored because not matching any filter: " + resource); } } } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to read candidate AMF class: " + resource, ex); } } else { if (traceEnabled) { log.trace("Ignored because not readable: " + resource); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); } return typesToRegister; } /** * 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(SystemPropertyUtils.resolvePlaceholders(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 passes the configured filters */ protected boolean applyFilters(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 class qualifies for AMF conversion * <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 metadata for the class to check * @return whether the class qualifies for AMF conversion */ protected boolean isCandidateForAmf(AnnotationMetadata metadata) { return (metadata.isConcrete() && metadata.isIndependent()); } private final class IncludeAllFilter implements TypeFilter{ public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return isCandidateForAmf(metadataReader.getAnnotationMetadata()); } } }