Lean  $LEAN_TAG$
Composer.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections;
18 using System.Collections.Concurrent;
19 using System.Collections.Generic;
20 using System.ComponentModel.Composition;
21 using System.ComponentModel.Composition.Hosting;
22 using System.ComponentModel.Composition.Primitives;
23 using System.ComponentModel.Composition.ReflectionModel;
24 using System.IO;
25 using System.Linq;
26 using System.Reflection;
27 using System.Threading;
28 using System.Threading.Tasks;
30 using QuantConnect.Data;
31 using QuantConnect.Logging;
32 
33 namespace QuantConnect.Util
34 {
35  /// <summary>
36  /// Provides methods for obtaining exported MEF instances
37  /// </summary>
38  public class Composer
39  {
40  private static string PluginDirectory;
41  private static readonly Lazy<Composer> LazyComposer = new Lazy<Composer>(
42  () =>
43  {
44  PluginDirectory = Config.Get("plugin-directory");
45  return new Composer();
46  });
47 
48  /// <summary>
49  /// Gets the singleton instance
50  /// </summary>
51  /// <remarks>Intentionally using a property so that when its gotten it will
52  /// trigger the lazy construction which will be after the right configuration
53  /// is loaded. See GH issue 3258</remarks>
54  public static Composer Instance => LazyComposer.Value;
55 
56  /// <summary>
57  /// Initializes a new instance of the <see cref="Composer"/> class. This type
58  /// is a light wrapper on top of an MEF <see cref="CompositionContainer"/>
59  /// </summary>
60  public Composer()
61  {
62  // Determine what directory to grab our assemblies from if not defined by 'composer-dll-directory' configuration key
63  var dllDirectoryString = Config.Get("composer-dll-directory");
64  if (string.IsNullOrWhiteSpace(dllDirectoryString))
65  {
66  // Check our appdomain directory for QC Dll's, for most cases this will be true and fine to use
67  if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.BaseDirectory) && Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "QuantConnect.*.dll").Any())
68  {
69  dllDirectoryString = AppDomain.CurrentDomain.BaseDirectory;
70  }
71  else
72  {
73  // Otherwise check out our parent and current working directory
74  // this is helpful for research because kernel appdomain defaults to kernel location
75  var currentDirectory = Directory.GetCurrentDirectory();
76  var parentDirectory = Directory.GetParent(currentDirectory)?.FullName ?? currentDirectory; // If parent == null will just use current
77 
78  // If our parent directory contains QC Dlls use it, otherwise default to current working directory
79  // In cloud and CLI research cases we expect the parent directory to contain the Dlls; but locally it's likely current directory
80  dllDirectoryString = Directory.GetFiles(parentDirectory, "QuantConnect.*.dll").Any() ? parentDirectory : currentDirectory;
81  }
82  }
83 
84  // Resolve full path name just to be safe
85  var primaryDllLookupDirectory = new DirectoryInfo(dllDirectoryString).FullName;
86  Log.Trace($"Composer(): Loading Assemblies from {primaryDllLookupDirectory}");
87 
88  var loadFromPluginDir = !string.IsNullOrWhiteSpace(PluginDirectory)
89  && Directory.Exists(PluginDirectory) &&
90  new DirectoryInfo(PluginDirectory).FullName != primaryDllLookupDirectory;
91  _composableParts = Task.Run(() =>
92  {
93  try
94  {
95  var catalogs = new List<ComposablePartCatalog>
96  {
97  new DirectoryCatalog(primaryDllLookupDirectory, "*.dll"),
98  new DirectoryCatalog(primaryDllLookupDirectory, "*.exe")
99  };
100  if (loadFromPluginDir)
101  {
102  catalogs.Add(new DirectoryCatalog(PluginDirectory, "*.dll"));
103  }
104  var aggregate = new AggregateCatalog(catalogs);
105  _compositionContainer = new CompositionContainer(aggregate);
106  return _compositionContainer.Catalog.Parts.ToList();
107  }
108  catch (Exception exception)
109  {
110  // ThreadAbortException is triggered when we shutdown ignore the error log
111  if (!(exception is ThreadAbortException))
112  {
113  Log.Error(exception);
114  }
115  }
116  return new List<ComposablePartDefinition>();
117  });
118 
119  // for performance we will load our assemblies and keep their exported types
120  // which is much faster that using CompositionContainer which uses reflexion
121  var exportedTypes = new ConcurrentBag<Type>();
122  var fileNames = Directory.EnumerateFiles(primaryDllLookupDirectory, $"{nameof(QuantConnect)}.*.dll");
123  if (loadFromPluginDir)
124  {
125  fileNames = fileNames.Concat(Directory.EnumerateFiles(PluginDirectory, $"{nameof(QuantConnect)}.*.dll"));
126  }
127 
128  // guarantee file name uniqueness
129  var files = new Dictionary<string, string>();
130  foreach (var filePath in fileNames)
131  {
132  var fileName = Path.GetFileName(filePath);
133  if (!string.IsNullOrEmpty(fileName))
134  {
135  files[fileName] = filePath;
136  }
137  }
138  Parallel.ForEach(files.Values,
139  file =>
140  {
141  try
142  {
143  foreach (var type in
144  Assembly.LoadFrom(file).ExportedTypes.Where(type => !type.IsAbstract && !type.IsInterface && !type.IsEnum))
145  {
146  exportedTypes.Add(type);
147  }
148  }
149  catch (Exception)
150  {
151  // ignored, just in case
152  }
153  }
154  );
155  _exportedTypes = new List<Type>(exportedTypes);
156  }
157 
158  private CompositionContainer _compositionContainer;
159  private readonly IReadOnlyList<Type> _exportedTypes;
160  private readonly Task<List<ComposablePartDefinition>> _composableParts;
161  private readonly object _exportedValuesLockObject = new object();
162  private readonly Dictionary<Type, IEnumerable> _exportedValues = new Dictionary<Type, IEnumerable>();
163 
164  /// <summary>
165  /// Gets the export matching the predicate
166  /// </summary>
167  /// <param name="predicate">Function used to pick which imported instance to return, if null the first instance is returned</param>
168  /// <returns>The only export matching the specified predicate</returns>
169  public T Single<T>(Func<T, bool> predicate)
170  {
171  if (predicate == null)
172  {
173  throw new ArgumentNullException(nameof(predicate));
174  }
175 
176  return GetExportedValues<T>().Single(predicate);
177  }
178 
179  /// <summary>
180  /// Adds the specified instance to this instance to allow it to be recalled via GetExportedValueByTypeName
181  /// </summary>
182  /// <typeparam name="T">The contract type</typeparam>
183  /// <param name="instance">The instance to add</param>
184  public void AddPart<T>(T instance)
185  {
186  lock (_exportedValuesLockObject)
187  {
188  IEnumerable values;
189  if (_exportedValues.TryGetValue(typeof(T), out values))
190  {
191  ((IList<T>)values).Add(instance);
192  }
193  else
194  {
195  values = new List<T> { instance };
196  _exportedValues[typeof(T)] = values;
197  }
198  }
199  }
200 
201  /// <summary>
202  /// Gets the first type T instance if any
203  /// </summary>
204  /// <typeparam name="T">The contract type</typeparam>
205  public T GetPart<T>()
206  {
207  return GetPart<T>(null);
208  }
209 
210  /// <summary>
211  /// Gets the first type T instance if any
212  /// </summary>
213  /// <typeparam name="T">The contract type</typeparam>
214  public T GetPart<T>(Func<T, bool> filter)
215  {
216  lock (_exportedValuesLockObject)
217  {
218  IEnumerable values;
219  if (_exportedValues.TryGetValue(typeof(T), out values))
220  {
221  return ((IList<T>)values).Where(x => filter == null || filter(x)).FirstOrDefault();
222  }
223  return default(T);
224  }
225  }
226 
227  /// <summary>
228  /// Will return all loaded types that are assignable to T type
229  /// </summary>
230  public IEnumerable<Type> GetExportedTypes<T>() where T : class
231  {
232  var type = typeof(T);
233  return _exportedTypes.Where(type1 =>
234  {
235  try
236  {
237  return type.IsAssignableFrom(type1);
238  }
239  catch
240  {
241  return false;
242  }
243  });
244  }
245 
246  /// <summary>
247  /// Extension method to searches the composition container for an export that has a matching type name. This function
248  /// will first try to match on Type.AssemblyQualifiedName, then Type.FullName, and finally on Type.Name
249  ///
250  /// This method will not throw if multiple types are found matching the name, it will just return the first one it finds.
251  /// </summary>
252  /// <typeparam name="T">The type of the export</typeparam>
253  /// <param name="typeName">The name of the type to find. This can be an assembly qualified name, a full name, or just the type's name</param>
254  /// <param name="forceTypeNameOnExisting">When false, if any existing instance of type T is found, it will be returned even if type name doesn't match.
255  /// This is useful in cases where a single global instance is desired, like for <see cref="IDataAggregator"/></param>
256  /// <returns>The export instance</returns>
257  public T GetExportedValueByTypeName<T>(string typeName, bool forceTypeNameOnExisting = true)
258  where T : class
259  {
260  try
261  {
262  T instance = null;
263  IEnumerable values;
264  var type = typeof(T);
265  lock (_exportedValuesLockObject)
266  {
267  if (_exportedValues.TryGetValue(type, out values))
268  {
269  // if we've already loaded this part, then just return the same one
270  instance = values.OfType<T>().FirstOrDefault(x => !forceTypeNameOnExisting || x.GetType().MatchesTypeName(typeName));
271  if (instance != null)
272  {
273  return instance;
274  }
275  }
276  }
277 
278  var typeT = _exportedTypes.Where(type1 =>
279  {
280  try
281  {
282  return type.IsAssignableFrom(type1) && type1.MatchesTypeName(typeName);
283  }
284  catch
285  {
286  return false;
287  }
288  })
289  .FirstOrDefault();
290 
291  if (typeT != null)
292  {
293  instance = (T)Activator.CreateInstance(typeT);
294  }
295 
296  if (instance == null)
297  {
298  // we want to get the requested part without instantiating each one of that type
299  var selectedPart = _composableParts.Result
300  .Where(x =>
301  {
302  try
303  {
304  var xType = ReflectionModelServices.GetPartType(x).Value;
305  return type.IsAssignableFrom(xType) && xType.MatchesTypeName(typeName);
306  }
307  catch
308  {
309  return false;
310  }
311  }
312  )
313  .FirstOrDefault();
314 
315  if (selectedPart == null)
316  {
317  throw new ArgumentException(
318  $"Unable to locate any exports matching the requested typeName: {typeName}. Type: {type}", nameof(typeName));
319  }
320 
321  var exportDefinition =
322  selectedPart.ExportDefinitions.First(
323  x => x.ContractName == AttributedModelServices.GetContractName(type));
324  instance = (T)selectedPart.CreatePart().GetExportedValue(exportDefinition);
325  }
326 
327  var exportedParts = instance.GetType().GetInterfaces()
328  .Where(interfaceType => interfaceType.GetCustomAttribute<InheritedExportAttribute>() != null);
329 
330  lock (_exportedValuesLockObject)
331  {
332  foreach (var export in exportedParts)
333  {
334  var exportList = _exportedValues.SingleOrDefault(kvp => kvp.Key == export).Value;
335 
336  // cache the new value for next time
337  if (exportList == null)
338  {
339  var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(export));
340  list.Add(instance);
341  _exportedValues[export] = list;
342  }
343  else
344  {
345  ((IList)exportList).Add(instance);
346  }
347  }
348 
349  return instance;
350  }
351  }
352  catch (ReflectionTypeLoadException err)
353  {
354  foreach (var exception in err.LoaderExceptions)
355  {
356  Log.Error(exception);
357  Log.Error(exception.ToString());
358  }
359 
360  if (err.InnerException != null) Log.Error(err.InnerException);
361 
362  throw;
363  }
364  }
365  /// <summary>
366  /// Gets all exports of type T
367  /// </summary>
368  public IEnumerable<T> GetExportedValues<T>()
369  {
370  try
371  {
372  lock (_exportedValuesLockObject)
373  {
374  IEnumerable values;
375  if (_exportedValues.TryGetValue(typeof(T), out values))
376  {
377  return values.OfType<T>();
378  }
379 
380  if (!_composableParts.IsCompleted)
381  {
382  _composableParts.Wait();
383  }
384  values = _compositionContainer.GetExportedValues<T>().ToList();
385  _exportedValues[typeof(T)] = values;
386  return values.OfType<T>();
387  }
388  }
389  catch (ReflectionTypeLoadException err)
390  {
391  foreach (var exception in err.LoaderExceptions)
392  {
393  Log.Error(exception);
394  }
395 
396  throw;
397  }
398  }
399 
400  /// <summary>
401  /// Clears the cache of exported values, causing new instances to be created.
402  /// </summary>
403  public void Reset()
404  {
405  lock (_exportedValuesLockObject)
406  {
407  _exportedValues.Clear();
408  }
409  }
410  }
411 }