diff --git a/src/Spring/Spring.Core.Configuration/Context/Attributes/AssemblyObjectDefinitionScanner.cs b/src/Spring/Spring.Core.Configuration/Context/Attributes/AssemblyObjectDefinitionScanner.cs new file mode 100644 index 00000000..d6d64ff8 --- /dev/null +++ b/src/Spring/Spring.Core.Configuration/Context/Attributes/AssemblyObjectDefinitionScanner.cs @@ -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(); + IAssemblyObjectDefinitionScanner WithAssemblyFilter(Predicate assemblyPredicate); + + IAssemblyObjectDefinitionScanner WithIncludeFilter(Predicate predicate); + IAssemblyObjectDefinitionScanner WithExcludeFilter(Predicate predicate); + + IAssemblyObjectDefinitionScanner IncludeTypes(IEnumerable typeSource); + IAssemblyObjectDefinitionScanner IncludeType(); + + IEnumerable Scan(); + } + + public class AssemblyObjectDefinitionScanner : IAssemblyObjectDefinitionScanner + { + private readonly List> _assemblyPredicates = new List>(); + + private readonly List> _excludePredicates = new List>(); + + private string _folderScanPath; + + private readonly List> _includePredicates = new List>(); + + private static ILog _logger = LogManager.GetLogger(typeof(AssemblyObjectDefinitionScanner)); + + private readonly List> _typeSources = new List>(); + + /// + /// Initializes a new instance of the AssemblyObjectDefinitionScanner class. + /// + public AssemblyObjectDefinitionScanner() + { + _folderScanPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + } + + /// + /// Initializes a new instance of the AssemblyObjectDefinitionScanner class. + /// + /// The folder scan path. + public AssemblyObjectDefinitionScanner(string folderScanPath) + { + _folderScanPath = folderScanPath; + } + + public IAssemblyObjectDefinitionScanner AssemblyHavingType() + { + _typeSources.Add(new AssemblyTypeSource((typeof(T).Assembly))); + return this; + } + + public IAssemblyObjectDefinitionScanner IncludeType() + { + _includePredicates.Add(t => t == typeof(T)); + return this; + } + + public IAssemblyObjectDefinitionScanner IncludeTypes(IEnumerable typeSource) + { + AssertUtils.ArgumentNotNull(typeSource, "typeSource"); + _typeSources.Add(typeSource); + _includePredicates.Add(t => typeSource.Any(t1 => t1 == t)); + return this; + } + + public IEnumerable Scan() + { + SetDefaultFiltersIfNeeded(); + + IList types = new List(); + + 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 assemblyPredicate) + { + _assemblyPredicates.Add(assemblyPredicate); + return this; + } + + public IAssemblyObjectDefinitionScanner WithExcludeFilter(Predicate predicate) + { + _excludePredicates.Add(predicate); + return this; + } + + public IAssemblyObjectDefinitionScanner WithIncludeFilter(Predicate 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 GetAllMatchingAssemblies() + { + IList assemblyCandidates = new List(); + + IEnumerable 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 assemblies = new List(); + + 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); + } + } + + } +} diff --git a/src/Spring/Spring.Core.Configuration/Context/Attributes/AssemblyTypeSource.cs b/src/Spring/Spring.Core.Configuration/Context/Attributes/AssemblyTypeSource.cs new file mode 100644 index 00000000..588f28fb --- /dev/null +++ b/src/Spring/Spring.Core.Configuration/Context/Attributes/AssemblyTypeSource.cs @@ -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 + { + private readonly _Assembly assembly; + + public AssemblyTypeSource(Assembly assembly) + { + AssertUtils.ArgumentNotNull(assembly, "assembly"); + this.assembly = assembly; + } + + public IEnumerator GetEnumerator() + { + foreach (var type in assembly.GetTypes()) + yield return type; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Core.Configuration/Context/Attributes/ConfigurationAttribute.cs b/src/Spring/Spring.Core.Configuration/Context/Attributes/ConfigurationAttribute.cs index 8bfdb6a4..348d2e28 100644 --- a/src/Spring/Spring.Core.Configuration/Context/Attributes/ConfigurationAttribute.cs +++ b/src/Spring/Spring.Core.Configuration/Context/Attributes/ConfigurationAttribute.cs @@ -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 /// /// [AttributeUsage(AttributeTargets.Class)] - public class ConfigurationAttribute : Attribute + public class ConfigurationAttribute : ComponentAttribute { /// diff --git a/src/Spring/Spring.Core.Configuration/Objects/Factory/Support/AssemblyScanningExtensionMethods.cs b/src/Spring/Spring.Core.Configuration/Objects/Factory/Support/AssemblyScanningExtensionMethods.cs index 01f4d1b5..e09141e2 100644 --- a/src/Spring/Spring.Core.Configuration/Objects/Factory/Support/AssemblyScanningExtensionMethods.cs +++ b/src/Spring/Spring.Core.Configuration/Objects/Factory/Support/AssemblyScanningExtensionMethods.cs @@ -11,23 +11,9 @@ namespace Spring.Objects.Factory.Support { public static class AssemblyScanningExtensionMethods { - private static ILog _logger = LogManager.GetLogger(typeof(AssemblyScanningExtensionMethods)); - - /// - /// Scans the assemblies for definitions. - /// - /// The registry. - /// The assembly scan path. - /// The assembly filename predicate. - /// The assembly metadata predicate. - /// - public static void ScanAssembliesAndRegisterDefinitions(this IObjectDefinitionRegistry registry, string assemblyScanPath, Func assemblyFilenamePredicate, Func assemblyMetadataPredicate) + public static void Scan(this IObjectDefinitionRegistry registry, IAssemblyObjectDefinitionScanner scanner) { - IEnumerable assemblies = GetAllMatchingAssemblies(assemblyScanPath, assemblyFilenamePredicate); - - assemblies = assemblies.Where(assembly => assemblyMetadataPredicate(assembly)); - - IEnumerable configTypes = GetAllConfigurationTypesDefinedIn(assemblies); + IEnumerable 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); } - /// - /// Scans the assemblies for definitions. - /// - /// The registry. - /// The assembly metadata predicate. - /// - public static void ScanAssembliesAndRegisterDefinitions(this IObjectDefinitionRegistry registry, Func assemblyMetadataPredicate) + public static void Scan(this IObjectDefinitionRegistry registry, Predicate typePredicate) { - ScanAssembliesAndRegisterDefinitions(registry, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), fn => true, assemblyMetadataPredicate); + Scan(registry, string.Empty, ta => true, typePredicate); } - /// - /// Scans the assemblies for definitions. - /// - /// The registry. - /// The assembly filename predicate. - /// The assembly metadata predicate. - /// - public static void ScanAssembliesAndRegisterDefinitions(this IObjectDefinitionRegistry registry, Func assemblyFilenamePredicate, Func assemblyMetadataPredicate) + public static void Scan(this IObjectDefinitionRegistry registry, string assemblyScanPath, Predicate assemblyPredicate, Predicate 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 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 assemblyPredicate, Predicate typePredicate) + { + Scan(registry, string.Empty, assemblyPredicate, typePredicate); + } + + public static void Scan(this IObjectDefinitionRegistry registry) + { + Scan(registry, new AssemblyObjectDefinitionScanner()); } /// @@ -79,59 +78,6 @@ namespace Spring.Objects.Factory.Support } } - /// - /// Gets all configuration types defined in the assemblies. - /// - /// The assemblies. - /// - private static IEnumerable GetAllConfigurationTypesDefinedIn(IEnumerable assemblies) - { - IList types = new List(); - - foreach (Assembly assembly in assemblies) - { - foreach (Type type in assembly.GetTypes()) - { - if (Attribute.GetCustomAttribute(type, typeof(ConfigurationAttribute), true) != null) - { - types.Add(type); - } - } - } - - return types; - } - - /// - /// Gets all matching assemblies. - /// - /// The assembly scan path. - /// The assembly filename predicate. - /// - private static IEnumerable GetAllMatchingAssemblies(string assemblyScanPath, Func assemblyFilenamePredicate) - { - IList assemblies = new List(); - - IEnumerable 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; - } - /// /// Regisiters the defintions for config types. /// diff --git a/src/Spring/Spring.Core.Configuration/Spring.Core.Configuration.2010.csproj b/src/Spring/Spring.Core.Configuration/Spring.Core.Configuration.2010.csproj index 730a69bc..78005eb8 100644 --- a/src/Spring/Spring.Core.Configuration/Spring.Core.Configuration.2010.csproj +++ b/src/Spring/Spring.Core.Configuration/Spring.Core.Configuration.2010.csproj @@ -41,6 +41,8 @@ + + diff --git a/test/Spring/Spring.Core.Configuration.Tests/Objects/Factory/Support/AssemblyScanningExtensionMethodsTests.cs b/test/Spring/Spring.Core.Configuration.Tests/Objects/Factory/Support/AssemblyScanningExtensionMethodsTests.cs index 1dce1d9d..0e55c52b 100644 --- a/test/Spring/Spring.Core.Configuration.Tests/Objects/Factory/Support/AssemblyScanningExtensionMethodsTests.cs +++ b/test/Spring/Spring.Core.Configuration.Tests/Objects/Factory/Support/AssemblyScanningExtensionMethodsTests.cs @@ -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);