initial rework of scanning to use AssemblyObjectDefinitionScanner class and more fluent filter-registration API

This commit is contained in:
sbohlen
2010-11-29 22:25:58 +00:00
parent d7c6bddc47
commit f886ca4fc7
6 changed files with 317 additions and 94 deletions

View File

@@ -0,0 +1,209 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using Common.Logging;
using Spring.Util;
namespace Spring.Context.Attributes
{
public interface IAssemblyObjectDefinitionScanner
{
IAssemblyObjectDefinitionScanner AssemblyHavingType<T>();
IAssemblyObjectDefinitionScanner WithAssemblyFilter(Predicate<Assembly> assemblyPredicate);
IAssemblyObjectDefinitionScanner WithIncludeFilter(Predicate<Type> predicate);
IAssemblyObjectDefinitionScanner WithExcludeFilter(Predicate<Type> predicate);
IAssemblyObjectDefinitionScanner IncludeTypes(IEnumerable<Type> typeSource);
IAssemblyObjectDefinitionScanner IncludeType<T>();
IEnumerable<Type> Scan();
}
public class AssemblyObjectDefinitionScanner : IAssemblyObjectDefinitionScanner
{
private readonly List<Predicate<Assembly>> _assemblyPredicates = new List<Predicate<Assembly>>();
private readonly List<Predicate<Type>> _excludePredicates = new List<Predicate<Type>>();
private string _folderScanPath;
private readonly List<Predicate<Type>> _includePredicates = new List<Predicate<Type>>();
private static ILog _logger = LogManager.GetLogger(typeof(AssemblyObjectDefinitionScanner));
private readonly List<IEnumerable<Type>> _typeSources = new List<IEnumerable<Type>>();
/// <summary>
/// Initializes a new instance of the AssemblyObjectDefinitionScanner class.
/// </summary>
public AssemblyObjectDefinitionScanner()
{
_folderScanPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}
/// <summary>
/// Initializes a new instance of the AssemblyObjectDefinitionScanner class.
/// </summary>
/// <param name="folderScanPath">The folder scan path.</param>
public AssemblyObjectDefinitionScanner(string folderScanPath)
{
_folderScanPath = folderScanPath;
}
public IAssemblyObjectDefinitionScanner AssemblyHavingType<T>()
{
_typeSources.Add(new AssemblyTypeSource((typeof(T).Assembly)));
return this;
}
public IAssemblyObjectDefinitionScanner IncludeType<T>()
{
_includePredicates.Add(t => t == typeof(T));
return this;
}
public IAssemblyObjectDefinitionScanner IncludeTypes(IEnumerable<Type> typeSource)
{
AssertUtils.ArgumentNotNull(typeSource, "typeSource");
_typeSources.Add(typeSource);
_includePredicates.Add(t => typeSource.Any(t1 => t1 == t));
return this;
}
public IEnumerable<Type> Scan()
{
SetDefaultFiltersIfNeeded();
IList<Type> types = new List<Type>();
foreach (Assembly assembly in GetAllMatchingAssemblies())
{
_typeSources.Add(new AssemblyTypeSource(assembly));
}
foreach (var typeSource in _typeSources)
{
foreach (Type type in typeSource)
{
if (IsIncludedType(type) && !IsExcludedType(type) && HasComponentAttribute(type))
{
types.Add(type);
}
}
}
return types;
}
public IAssemblyObjectDefinitionScanner WithAssemblyFilter(Predicate<Assembly> assemblyPredicate)
{
_assemblyPredicates.Add(assemblyPredicate);
return this;
}
public IAssemblyObjectDefinitionScanner WithExcludeFilter(Predicate<Type> predicate)
{
_excludePredicates.Add(predicate);
return this;
}
public IAssemblyObjectDefinitionScanner WithIncludeFilter(Predicate<Type> predicate)
{
_includePredicates.Add(predicate);
return this;
}
protected virtual bool IsExcludedType(Type type)
{
foreach (var exclude in _excludePredicates)
{
if (exclude(type))
{
return true;
}
}
return false;
}
protected virtual bool IsIncludedType(Type type)
{
foreach (var include in _includePredicates)
{
if (include(type))
{
return true;
}
}
return false;
}
private bool HasComponentAttribute(Type type)
{
return Attribute.GetCustomAttribute(type, typeof(ConfigurationAttribute), true) != null;
}
private IEnumerable<Assembly> GetAllMatchingAssemblies()
{
IList<Assembly> assemblyCandidates = new List<Assembly>();
IEnumerable<string> files = Directory.GetFiles(_folderScanPath, "*.dll");
foreach (string file in files)
{
try
{
assemblyCandidates.Add(Assembly.LoadFrom(file));
}
catch (Exception ex)
{
//log and swallow everything that might go wrong here...
if (_logger.IsDebugEnabled)
_logger.Debug("Failed to load type while scanning Assemblies for Defintions!", ex);
}
}
IList<Assembly> assemblies = new List<Assembly>();
foreach (Assembly assemblyCandidate in assemblyCandidates)
{
foreach (var include in _assemblyPredicates)
{
if (include(assemblyCandidate))
{
assemblies.Add(assemblyCandidate);
break;
}
}
}
return assemblies;
}
private void SetDefaultFiltersIfNeeded()
{
if (_includePredicates.Count == 0)
{
_includePredicates.Add(t => true);
}
if (_excludePredicates.Count == 0)
{
_excludePredicates.Add(t => false);
}
if (_assemblyPredicates.Count == 0)
{
_assemblyPredicates.Add(a => true);
}
}
}
}

View File

@@ -0,0 +1,51 @@
#region License
/*
* Copyright © 2002-2008 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.
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using Spring.Util;
namespace Spring.Context.Attributes
{
public class AssemblyTypeSource : IEnumerable<Type>
{
private readonly _Assembly assembly;
public AssemblyTypeSource(Assembly assembly)
{
AssertUtils.ArgumentNotNull(assembly, "assembly");
this.assembly = assembly;
}
public IEnumerator<Type> GetEnumerator()
{
foreach (var type in assembly.GetTypes())
yield return type;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using Spring.Stereotype;
namespace Spring.Context.Attributes
{
@@ -25,7 +26,7 @@ namespace Spring.Context.Attributes
/// </ul>
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ConfigurationAttribute : Attribute
public class ConfigurationAttribute : ComponentAttribute
{
/// <summary>

View File

@@ -11,23 +11,9 @@ namespace Spring.Objects.Factory.Support
{
public static class AssemblyScanningExtensionMethods
{
private static ILog _logger = LogManager.GetLogger(typeof(AssemblyScanningExtensionMethods));
/// <summary>
/// Scans the assemblies for definitions.
/// </summary>
/// <param name="registry">The registry.</param>
/// <param name="assemblyScanPath">The assembly scan path.</param>
/// <param name="assemblyFilenamePredicate">The assembly filename predicate.</param>
/// <param name="assemblyMetadataPredicate">The assembly metadata predicate.</param>
/// <returns></returns>
public static void ScanAssembliesAndRegisterDefinitions(this IObjectDefinitionRegistry registry, string assemblyScanPath, Func<string, bool> assemblyFilenamePredicate, Func<Assembly, bool> assemblyMetadataPredicate)
public static void Scan(this IObjectDefinitionRegistry registry, IAssemblyObjectDefinitionScanner scanner)
{
IEnumerable<Assembly> assemblies = GetAllMatchingAssemblies(assemblyScanPath, assemblyFilenamePredicate);
assemblies = assemblies.Where(assembly => assemblyMetadataPredicate(assembly));
IEnumerable<Type> configTypes = GetAllConfigurationTypesDefinedIn(assemblies);
IEnumerable<Type> configTypes = scanner.Scan();
//if we have at least one config class, ensure the post-processor is registered
if (configTypes.Count() > 0)
@@ -38,32 +24,45 @@ namespace Spring.Objects.Factory.Support
RegisiterDefintionsForConfigTypes(configTypes, registry);
}
/// <summary>
/// Scans the assemblies for definitions.
/// </summary>
/// <param name="registry">The registry.</param>
/// <param name="assemblyMetadataPredicate">The assembly metadata predicate.</param>
/// <returns></returns>
public static void ScanAssembliesAndRegisterDefinitions(this IObjectDefinitionRegistry registry, Func<Assembly, bool> assemblyMetadataPredicate)
public static void Scan(this IObjectDefinitionRegistry registry, Predicate<Type> typePredicate)
{
ScanAssembliesAndRegisterDefinitions(registry, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), fn => true, assemblyMetadataPredicate);
Scan(registry, string.Empty, ta => true, typePredicate);
}
/// <summary>
/// Scans the assemblies for definitions.
/// </summary>
/// <param name="registry">The registry.</param>
/// <param name="assemblyFilenamePredicate">The assembly filename predicate.</param>
/// <param name="assemblyMetadataPredicate">The assembly metadata predicate.</param>
/// <returns></returns>
public static void ScanAssembliesAndRegisterDefinitions(this IObjectDefinitionRegistry registry, Func<string, bool> assemblyFilenamePredicate, Func<Assembly, bool> assemblyMetadataPredicate)
public static void Scan(this IObjectDefinitionRegistry registry, string assemblyScanPath, Predicate<Assembly> assemblyPredicate, Predicate<Type> typePredicate)
{
ScanAssembliesAndRegisterDefinitions(registry, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), assemblyFilenamePredicate, assemblyMetadataPredicate);
IAssemblyObjectDefinitionScanner scanner;
//create a scanner instance using the scan path (or not!) as appropropriate
if (string.IsNullOrEmpty(assemblyScanPath))
{
scanner = new AssemblyObjectDefinitionScanner();
}
else
{
scanner = new AssemblyObjectDefinitionScanner(assemblyScanPath);
}
//configure the scanner per the provided constraints
scanner.WithAssemblyFilter(assemblyPredicate).WithIncludeFilter(typePredicate);
//pass the scanner to primary Scan method to actually do the work
Scan(registry, scanner);
}
public static void ScanAssembliesAndRegisterDefinitions(this IObjectDefinitionRegistry registry)
public static void Scan(this IObjectDefinitionRegistry registry, Predicate<Assembly> assemblyPredicate)
{
ScanAssembliesAndRegisterDefinitions(registry, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), fn => true, a => true);
Scan(registry, string.Empty, assemblyPredicate, t => true);
}
public static void Scan(this IObjectDefinitionRegistry registry, Predicate<Assembly> assemblyPredicate, Predicate<Type> typePredicate)
{
Scan(registry, string.Empty, assemblyPredicate, typePredicate);
}
public static void Scan(this IObjectDefinitionRegistry registry)
{
Scan(registry, new AssemblyObjectDefinitionScanner());
}
/// <summary>
@@ -79,59 +78,6 @@ namespace Spring.Objects.Factory.Support
}
}
/// <summary>
/// Gets all configuration types defined in the assemblies.
/// </summary>
/// <param name="assemblies">The assemblies.</param>
/// <returns></returns>
private static IEnumerable<Type> GetAllConfigurationTypesDefinedIn(IEnumerable<Assembly> assemblies)
{
IList<Type> types = new List<Type>();
foreach (Assembly assembly in assemblies)
{
foreach (Type type in assembly.GetTypes())
{
if (Attribute.GetCustomAttribute(type, typeof(ConfigurationAttribute), true) != null)
{
types.Add(type);
}
}
}
return types;
}
/// <summary>
/// Gets all matching assemblies.
/// </summary>
/// <param name="assemblyScanPath">The assembly scan path.</param>
/// <param name="assemblyFilenamePredicate">The assembly filename predicate.</param>
/// <returns></returns>
private static IEnumerable<Assembly> GetAllMatchingAssemblies(string assemblyScanPath, Func<string, bool> assemblyFilenamePredicate)
{
IList<Assembly> assemblies = new List<Assembly>();
IEnumerable<string> files = Directory.GetFiles(assemblyScanPath, "*.dll").Where(s => assemblyFilenamePredicate(Path.GetFileName(s)));
foreach (string file in files)
{
try
{
assemblies.Add(Assembly.LoadFrom(file));
}
catch (Exception ex)
{
//log and swallow everything that might go wrong here...
if (_logger.IsDebugEnabled)
_logger.Debug("Failed to load type while scanning Assemblies for Defintions!", ex);
}
}
return assemblies;
}
/// <summary>
/// Regisiters the defintions for config types.
/// </summary>

View File

@@ -41,6 +41,8 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Context\Advice\SpringObjectMethodInterceptor.cs" />
<Compile Include="Context\Attributes\AssemblyObjectDefinitionScanner.cs" />
<Compile Include="Context\Attributes\AssemblyTypeSource.cs" />
<Compile Include="Context\Attributes\ConfigurationAttribute.cs" />
<Compile Include="Context\Attributes\ConfigurationClass.cs" />
<Compile Include="Context\Attributes\ConfigurationClassMethod.cs" />

View File

@@ -7,6 +7,7 @@ using System.Reflection;
using System.Diagnostics;
using Spring.Context.Config;
using Spring.Context.Support;
using Spring.Context.Attributes;
namespace Spring.Objects.Factory.Support
{
@@ -14,30 +15,43 @@ namespace Spring.Objects.Factory.Support
public class AssemblyScanningExtensionMethodsTests
{
[Test]
public void Integration_Scenario_With_Assembly_Filename_And_Assembly_Metadata_Filtering()
public void Integration_Scenario_With_Assembly_Filtering()
{
GenericApplicationContext context = new GenericApplicationContext();
context.ScanAssembliesAndRegisterDefinitions(fn => fn.StartsWith("Spring."), assy => assy.GetTypes().Any(type => type.FullName.Contains(typeof(MarkerTypeForScannerToFind).Name)));
context.Scan(a => a.GetName().Name.StartsWith("Spring.Core.Configuration."));
context.Refresh();
AssertExpectedObjectsAreRegisteredWith(context);
}
[Test]
//TODO: double check to ensure that this test really SHOULD pass...seems like its finding too wide a collection of assy's to scan... :(
public void Integration_Scenario_With_Assembly_Filtering_Containing_Specific_Type()
{
GenericApplicationContext context = new GenericApplicationContext();
context.Scan(assy => assy.GetTypes().Any(type => type.FullName.Contains(typeof(MarkerTypeForScannerToFind).Name)));
context.Refresh();
AssertExpectedObjectsAreRegisteredWith(context);
}
[Test]
public void Integration_Scenario_With_Assembly_Metadata_Filtering()
public void Integration_Scenario_With_Type_Filtering()
{
GenericApplicationContext context = new GenericApplicationContext();
context.ScanAssembliesAndRegisterDefinitions(assy => assy.GetTypes().Any(type => type.FullName.Contains(typeof(MarkerTypeForScannerToFind).Name)));
context.Scan(type => ((Type)type).FullName.Contains(typeof(TheConfigurationClass).Name));
context.Refresh();
AssertExpectedObjectsAreRegisteredWith(context);
}
[Test]
public void Integration_Scenario_With_Default_of_No_Filtering()
{
GenericApplicationContext context = new GenericApplicationContext();
context.ScanAssembliesAndRegisterDefinitions();
context.Scan();
context.Refresh();
AssertExpectedObjectsAreRegisteredWith(context);