/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* 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.
*/
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.ComponentModel.Composition.ReflectionModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using QuantConnect.Configuration;
using QuantConnect.Data;
using QuantConnect.Logging;
namespace QuantConnect.Util
{
///
/// Provides methods for obtaining exported MEF instances
///
public class Composer
{
private static string PluginDirectory;
private static readonly Lazy LazyComposer = new Lazy(
() =>
{
PluginDirectory = Config.Get("plugin-directory");
return new Composer();
});
///
/// Gets the singleton instance
///
/// Intentionally using a property so that when its gotten it will
/// trigger the lazy construction which will be after the right configuration
/// is loaded. See GH issue 3258
public static Composer Instance => LazyComposer.Value;
///
/// Initializes a new instance of the class. This type
/// is a light wrapper on top of an MEF
///
public Composer()
{
// Determine what directory to grab our assemblies from if not defined by 'composer-dll-directory' configuration key
var dllDirectoryString = Config.Get("composer-dll-directory");
if (string.IsNullOrWhiteSpace(dllDirectoryString))
{
// Check our appdomain directory for QC Dll's, for most cases this will be true and fine to use
if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.BaseDirectory) && Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "QuantConnect.*.dll").Any())
{
dllDirectoryString = AppDomain.CurrentDomain.BaseDirectory;
}
else
{
// Otherwise check out our parent and current working directory
// this is helpful for research because kernel appdomain defaults to kernel location
var currentDirectory = Directory.GetCurrentDirectory();
var parentDirectory = Directory.GetParent(currentDirectory)?.FullName ?? currentDirectory; // If parent == null will just use current
// If our parent directory contains QC Dlls use it, otherwise default to current working directory
// In cloud and CLI research cases we expect the parent directory to contain the Dlls; but locally it's likely current directory
dllDirectoryString = Directory.GetFiles(parentDirectory, "QuantConnect.*.dll").Any() ? parentDirectory : currentDirectory;
}
}
// Resolve full path name just to be safe
var primaryDllLookupDirectory = new DirectoryInfo(dllDirectoryString).FullName;
Log.Trace($"Composer(): Loading Assemblies from {primaryDllLookupDirectory}");
var loadFromPluginDir = !string.IsNullOrWhiteSpace(PluginDirectory)
&& Directory.Exists(PluginDirectory) &&
new DirectoryInfo(PluginDirectory).FullName != primaryDllLookupDirectory;
_composableParts = Task.Run(() =>
{
try
{
var catalogs = new List
{
new DirectoryCatalog(primaryDllLookupDirectory, "*.dll"),
new DirectoryCatalog(primaryDllLookupDirectory, "*.exe")
};
if (loadFromPluginDir)
{
catalogs.Add(new DirectoryCatalog(PluginDirectory, "*.dll"));
}
var aggregate = new AggregateCatalog(catalogs);
_compositionContainer = new CompositionContainer(aggregate);
return _compositionContainer.Catalog.Parts.ToList();
}
catch (Exception exception)
{
// ThreadAbortException is triggered when we shutdown ignore the error log
if (!(exception is ThreadAbortException))
{
Log.Error(exception);
}
}
return new List();
});
// for performance we will load our assemblies and keep their exported types
// which is much faster that using CompositionContainer which uses reflexion
var exportedTypes = new ConcurrentBag();
var fileNames = Directory.EnumerateFiles(primaryDllLookupDirectory, $"{nameof(QuantConnect)}.*.dll");
if (loadFromPluginDir)
{
fileNames = fileNames.Concat(Directory.EnumerateFiles(PluginDirectory, $"{nameof(QuantConnect)}.*.dll"));
}
// guarantee file name uniqueness
var files = new Dictionary();
foreach (var filePath in fileNames)
{
var fileName = Path.GetFileName(filePath);
if (!string.IsNullOrEmpty(fileName))
{
files[fileName] = filePath;
}
}
Parallel.ForEach(files.Values,
file =>
{
try
{
foreach (var type in
Assembly.LoadFrom(file).ExportedTypes.Where(type => !type.IsAbstract && !type.IsInterface && !type.IsEnum))
{
exportedTypes.Add(type);
}
}
catch (Exception)
{
// ignored, just in case
}
}
);
_exportedTypes = new List(exportedTypes);
}
private CompositionContainer _compositionContainer;
private readonly IReadOnlyList _exportedTypes;
private readonly Task> _composableParts;
private readonly object _exportedValuesLockObject = new object();
private readonly Dictionary _exportedValues = new Dictionary();
///
/// Gets the export matching the predicate
///
/// Function used to pick which imported instance to return, if null the first instance is returned
/// The only export matching the specified predicate
public T Single(Func predicate)
{
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}
return GetExportedValues().Single(predicate);
}
///
/// Adds the specified instance to this instance to allow it to be recalled via GetExportedValueByTypeName
///
/// The contract type
/// The instance to add
public void AddPart(T instance)
{
lock (_exportedValuesLockObject)
{
IEnumerable values;
if (_exportedValues.TryGetValue(typeof(T), out values))
{
((IList)values).Add(instance);
}
else
{
values = new List { instance };
_exportedValues[typeof(T)] = values;
}
}
}
///
/// Gets the first type T instance if any
///
/// The contract type
public T GetPart()
{
return GetPart(null);
}
///
/// Gets the first type T instance if any
///
/// The contract type
public T GetPart(Func filter)
{
return GetParts().Where(x => filter == null || filter(x)).FirstOrDefault();
}
///
/// Gets all parts of type T instance if any
///
/// The contract type
public IEnumerable GetParts()
{
lock (_exportedValuesLockObject)
{
IEnumerable values;
if (_exportedValues.TryGetValue(typeof(T), out values))
{
return ((IEnumerable)values).ToList();
}
return Enumerable.Empty();
}
}
///
/// Will return all loaded types that are assignable to T type
///
public IEnumerable GetExportedTypes() where T : class
{
var type = typeof(T);
return _exportedTypes.Where(type1 =>
{
try
{
return type.IsAssignableFrom(type1);
}
catch
{
return false;
}
});
}
///
/// Extension method to searches the composition container for an export that has a matching type name. This function
/// will first try to match on Type.AssemblyQualifiedName, then Type.FullName, and finally on Type.Name
///
/// This method will not throw if multiple types are found matching the name, it will just return the first one it finds.
///
/// The type of the export
/// The name of the type to find. This can be an assembly qualified name, a full name, or just the type's name
/// When false, if any existing instance of type T is found, it will be returned even if type name doesn't match.
/// This is useful in cases where a single global instance is desired, like for
/// The export instance
public T GetExportedValueByTypeName(string typeName, bool forceTypeNameOnExisting = true)
where T : class
{
try
{
T instance = null;
IEnumerable values;
var type = typeof(T);
lock (_exportedValuesLockObject)
{
if (_exportedValues.TryGetValue(type, out values))
{
// if we've already loaded this part, then just return the same one
instance = values.OfType().FirstOrDefault(x => !forceTypeNameOnExisting || x.GetType().MatchesTypeName(typeName));
if (instance != null)
{
return instance;
}
}
}
var typeT = _exportedTypes.Where(type1 =>
{
try
{
return type.IsAssignableFrom(type1) && type1.MatchesTypeName(typeName);
}
catch
{
return false;
}
})
.FirstOrDefault();
if (typeT != null)
{
instance = (T)Activator.CreateInstance(typeT);
}
if (instance == null)
{
// we want to get the requested part without instantiating each one of that type
var selectedPart = _composableParts.Result
.Where(x =>
{
try
{
var xType = ReflectionModelServices.GetPartType(x).Value;
return type.IsAssignableFrom(xType) && xType.MatchesTypeName(typeName);
}
catch
{
return false;
}
}
)
.FirstOrDefault();
if (selectedPart == null)
{
throw new ArgumentException(
$"Unable to locate any exports matching the requested typeName: {typeName}. Type: {type}", nameof(typeName));
}
var exportDefinition =
selectedPart.ExportDefinitions.First(
x => x.ContractName == AttributedModelServices.GetContractName(type));
instance = (T)selectedPart.CreatePart().GetExportedValue(exportDefinition);
}
var exportedParts = instance.GetType().GetInterfaces()
.Where(interfaceType => interfaceType.GetCustomAttribute() != null);
lock (_exportedValuesLockObject)
{
foreach (var export in exportedParts)
{
var exportList = _exportedValues.SingleOrDefault(kvp => kvp.Key == export).Value;
// cache the new value for next time
if (exportList == null)
{
var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(export));
list.Add(instance);
_exportedValues[export] = list;
}
else
{
((IList)exportList).Add(instance);
}
}
return instance;
}
}
catch (ReflectionTypeLoadException err)
{
foreach (var exception in err.LoaderExceptions)
{
Log.Error(exception);
Log.Error(exception.ToString());
}
if (err.InnerException != null) Log.Error(err.InnerException);
throw;
}
}
///
/// Gets all exports of type T
///
public IEnumerable GetExportedValues()
{
try
{
lock (_exportedValuesLockObject)
{
IEnumerable values;
if (_exportedValues.TryGetValue(typeof(T), out values))
{
return values.OfType();
}
if (!_composableParts.IsCompleted)
{
_composableParts.Wait();
}
values = _compositionContainer.GetExportedValues().ToList();
_exportedValues[typeof(T)] = values;
return values.OfType();
}
}
catch (ReflectionTypeLoadException err)
{
foreach (var exception in err.LoaderExceptions)
{
Log.Error(exception);
}
throw;
}
}
///
/// Clears the cache of exported values, causing new instances to be created.
///
public void Reset()
{
lock (_exportedValuesLockObject)
{
_exportedValues.Clear();
}
}
}
}