PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/class/System.ComponentModel.Composition.4.5/src/ComponentModel/System/ComponentModel/Composition/Hosting/CatalogExportProvider.cs

https://bitbucket.org/danipen/mono
C# | 955 lines | 681 code | 128 blank | 146 comment | 84 complexity | f23835aaf38ce9790949dcb37de850a0 MD5 | raw file
Possible License(s): Unlicense, Apache-2.0, LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
  1. // -----------------------------------------------------------------------
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. // -----------------------------------------------------------------------
  4. using System;
  5. using System.Collections.Generic;
  6. using System.ComponentModel.Composition.Diagnostics;
  7. using System.ComponentModel.Composition.Primitives;
  8. using System.ComponentModel.Composition.ReflectionModel;
  9. using System.Diagnostics;
  10. using System.Diagnostics.CodeAnalysis;
  11. using System.Diagnostics.Contracts;
  12. using System.Globalization;
  13. using System.Linq;
  14. using System.Runtime.CompilerServices;
  15. using System.Threading;
  16. using Microsoft.Internal;
  17. using Microsoft.Internal.Collections;
  18. namespace System.ComponentModel.Composition.Hosting
  19. {
  20. public partial class CatalogExportProvider : ExportProvider, IDisposable
  21. {
  22. private class InnerCatalogExportProvider : ExportProvider
  23. {
  24. Func<ImportDefinition, AtomicComposition, IEnumerable<Export>> _getExportsCore;
  25. public InnerCatalogExportProvider(Func<ImportDefinition, AtomicComposition, IEnumerable<Export>> getExportsCore )
  26. {
  27. this._getExportsCore = getExportsCore;
  28. }
  29. protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
  30. {
  31. Assumes.NotNull(this._getExportsCore);
  32. return this._getExportsCore(definition, atomicComposition);
  33. }
  34. }
  35. private readonly CompositionLock _lock;
  36. private Dictionary<ComposablePartDefinition, CatalogPart> _activatedParts = new Dictionary<ComposablePartDefinition, CatalogPart>();
  37. private HashSet<ComposablePartDefinition> _rejectedParts = new HashSet<ComposablePartDefinition>();
  38. private ConditionalWeakTable<object, List<ComposablePart>> _gcRoots;
  39. private HashSet<IDisposable> _partsToDispose = new HashSet<IDisposable>();
  40. private ComposablePartCatalog _catalog;
  41. private volatile bool _isDisposed = false;
  42. private volatile bool _isRunning = false;
  43. private ExportProvider _sourceProvider;
  44. private ImportEngine _importEngine;
  45. private CompositionOptions _compositionOptions;
  46. private ExportProvider _innerExportProvider;
  47. /// <summary>
  48. /// Initializes a new instance of the <see cref="CatalogExportProvider"/> class.
  49. /// </summary>
  50. /// <param name="catalog">
  51. /// The <see cref="ComposablePartCatalog"/> that the <see cref="CatalogExportProvider"/>
  52. /// uses to produce <see cref="Export"/> objects.
  53. /// </param>
  54. /// <exception cref="ArgumentNullException">
  55. /// <paramref name="catalog"/> is <see langword="null"/>.
  56. /// </exception>
  57. public CatalogExportProvider(ComposablePartCatalog catalog)
  58. : this(catalog, CompositionOptions.Default)
  59. {
  60. }
  61. public CatalogExportProvider(ComposablePartCatalog catalog, bool isThreadSafe)
  62. : this(catalog, isThreadSafe ? CompositionOptions.IsThreadSafe : CompositionOptions.Default)
  63. {
  64. }
  65. public CatalogExportProvider(ComposablePartCatalog catalog, CompositionOptions compositionOptions)
  66. {
  67. Requires.NotNull(catalog, "catalog");
  68. if (compositionOptions > (CompositionOptions.DisableSilentRejection | CompositionOptions.IsThreadSafe | CompositionOptions.ExportCompositionService))
  69. {
  70. throw new ArgumentOutOfRangeException("compositionOptions");
  71. }
  72. this._catalog = catalog;
  73. this._compositionOptions = compositionOptions;
  74. var notifyCatalogChanged = this._catalog as INotifyComposablePartCatalogChanged;
  75. if (notifyCatalogChanged != null)
  76. {
  77. notifyCatalogChanged.Changing += this.OnCatalogChanging;
  78. }
  79. CompositionScopeDefinition scopeDefinition = this._catalog as CompositionScopeDefinition;
  80. if (scopeDefinition != null)
  81. {
  82. this._innerExportProvider = new AggregateExportProvider(new ScopeManager(this, scopeDefinition), new InnerCatalogExportProvider(InternalGetExportsCore));
  83. }
  84. else
  85. {
  86. this._innerExportProvider = new InnerCatalogExportProvider(InternalGetExportsCore);
  87. }
  88. this._lock = new CompositionLock(compositionOptions.HasFlag(CompositionOptions.IsThreadSafe));
  89. }
  90. /// <summary>
  91. /// Gets the composable part catalog that the provider users to
  92. /// produce exports.
  93. /// </summary>
  94. /// <value>
  95. /// The <see cref="ComposablePartCatalog"/> that the
  96. /// <see cref="CatalogExportProvider"/>
  97. /// uses to produce <see cref="Export"/> objects.
  98. /// </value>
  99. /// <exception cref="ObjectDisposedException">
  100. /// The <see cref="CompositionContainer"/> has been disposed of.
  101. /// </exception>
  102. public ComposablePartCatalog Catalog
  103. {
  104. get
  105. {
  106. ThrowIfDisposed();
  107. Contract.Ensures(Contract.Result<ComposablePartCatalog>() != null);
  108. return this._catalog;
  109. }
  110. }
  111. /// <summary>
  112. /// Gets the export provider which provides the provider access to additional
  113. /// exports.
  114. /// </summary>
  115. /// <value>
  116. /// The <see cref="ExportProvider"/> which provides the
  117. /// <see cref="CatalogExportProvider"/> access to additional
  118. /// <see cref="Export"/> objects. The default is <see langword="null"/>.
  119. /// </value>
  120. /// <exception cref="ArgumentNullException">
  121. /// <paramref name="value"/> is <see langword="null"/>.
  122. /// </exception>
  123. /// <exception cref="InvalidOperationException">
  124. /// This property has already been set.
  125. /// <para>
  126. /// -or-
  127. /// </para>
  128. /// The methods on the <see cref="CatalogExportProvider"/>
  129. /// have already been accessed.
  130. /// </exception>
  131. /// <exception cref="ObjectDisposedException">
  132. /// The <see cref="CatalogExportProvider"/> has been disposed of.
  133. /// </exception>
  134. /// <remarks>
  135. /// This property must be set before accessing any methods on the
  136. /// <see cref="CatalogExportProvider"/>.
  137. /// </remarks>
  138. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification="EnsureCanSet ensures that the property is set only once, Dispose is not required")]
  139. public ExportProvider SourceProvider
  140. {
  141. get
  142. {
  143. this.ThrowIfDisposed();
  144. using (this._lock.LockStateForRead())
  145. {
  146. return this._sourceProvider;
  147. }
  148. }
  149. set
  150. {
  151. this.ThrowIfDisposed();
  152. Requires.NotNull(value, "value");
  153. ImportEngine newImportEngine = null;
  154. AggregateExportProvider aggregateExportProvider = null;
  155. ExportProvider sourceProvider = value;
  156. bool isThrowing = true;
  157. try
  158. {
  159. newImportEngine = new ImportEngine(sourceProvider, this._compositionOptions);
  160. sourceProvider.ExportsChanging += this.OnExportsChangingInternal;
  161. using (this._lock.LockStateForWrite())
  162. {
  163. this.EnsureCanSet(this._sourceProvider);
  164. this._sourceProvider = sourceProvider;
  165. this._importEngine = newImportEngine;
  166. isThrowing = false;
  167. }
  168. }
  169. finally
  170. {
  171. if (isThrowing)
  172. {
  173. sourceProvider.ExportsChanging -= this.OnExportsChangingInternal;
  174. newImportEngine.Dispose();
  175. if (aggregateExportProvider != null)
  176. {
  177. aggregateExportProvider.Dispose();
  178. }
  179. }
  180. }
  181. }
  182. }
  183. /// <summary>
  184. /// Releases unmanaged and - optionally - managed resources
  185. /// </summary>
  186. public void Dispose()
  187. {
  188. this.Dispose(true);
  189. GC.SuppressFinalize(this);
  190. }
  191. /// <summary>
  192. /// Releases unmanaged and - optionally - managed resources
  193. /// </summary>
  194. /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  195. protected virtual void Dispose(bool disposing)
  196. {
  197. if (disposing)
  198. {
  199. if (!this._isDisposed)
  200. {
  201. bool disposeLock = false;
  202. INotifyComposablePartCatalogChanged catalogToUnsubscribeFrom = null;
  203. HashSet<IDisposable> partsToDispose = null;
  204. ImportEngine importEngine = null;
  205. ExportProvider sourceProvider = null;
  206. AggregateExportProvider aggregateExportProvider = null;
  207. try
  208. {
  209. using (this._lock.LockStateForWrite())
  210. {
  211. if (!this._isDisposed)
  212. {
  213. catalogToUnsubscribeFrom = this._catalog as INotifyComposablePartCatalogChanged;
  214. this._catalog = null;
  215. aggregateExportProvider = this._innerExportProvider as AggregateExportProvider;
  216. this._innerExportProvider = null;
  217. sourceProvider = this._sourceProvider;
  218. this._sourceProvider = null;
  219. importEngine = this._importEngine;
  220. this._importEngine = null;
  221. partsToDispose = this._partsToDispose;
  222. this._gcRoots = null;
  223. disposeLock = true;
  224. this._isDisposed = true;
  225. }
  226. }
  227. }
  228. finally
  229. {
  230. if (catalogToUnsubscribeFrom != null)
  231. {
  232. catalogToUnsubscribeFrom.Changing -= this.OnCatalogChanging;
  233. }
  234. if (aggregateExportProvider != null)
  235. {
  236. aggregateExportProvider.Dispose();
  237. }
  238. if (sourceProvider != null)
  239. {
  240. sourceProvider.ExportsChanging -= this.OnExportsChangingInternal;
  241. }
  242. if (importEngine != null)
  243. {
  244. importEngine.Dispose();
  245. }
  246. if (partsToDispose != null)
  247. {
  248. foreach (var part in partsToDispose)
  249. {
  250. part.Dispose();
  251. }
  252. }
  253. if (disposeLock)
  254. {
  255. this._lock.Dispose();
  256. }
  257. }
  258. }
  259. }
  260. }
  261. /// <summary>
  262. /// Returns all exports that match the conditions of the specified import.
  263. /// </summary>
  264. /// <param name="definition">The <see cref="ImportDefinition"/> that defines the conditions of the
  265. /// <see cref="Export"/> to get.</param>
  266. /// <returns></returns>
  267. /// <result>
  268. /// An <see cref="IEnumerable{T}"/> of <see cref="Export"/> objects that match
  269. /// the conditions defined by <see cref="ImportDefinition"/>, if found; otherwise, an
  270. /// empty <see cref="IEnumerable{T}"/>.
  271. /// </result>
  272. /// <remarks>
  273. /// <note type="inheritinfo">
  274. /// The implementers should not treat the cardinality-related mismatches as errors, and are not
  275. /// expected to throw exceptions in those cases.
  276. /// For instance, if the import requests exactly one export and the provider has no matching exports or more than one,
  277. /// it should return an empty <see cref="IEnumerable{T}"/> of <see cref="Export"/>.
  278. /// </note>
  279. /// </remarks>
  280. protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
  281. {
  282. this.ThrowIfDisposed();
  283. this.EnsureRunning();
  284. Assumes.NotNull(this._innerExportProvider);
  285. IEnumerable<Export> exports;
  286. this._innerExportProvider.TryGetExports(definition, atomicComposition, out exports);
  287. return exports;
  288. }
  289. private IEnumerable<Export> InternalGetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
  290. {
  291. this.ThrowIfDisposed();
  292. this.EnsureRunning();
  293. // Use the version of the catalog appropriate to this atomicComposition
  294. ComposablePartCatalog currentCatalog = atomicComposition.GetValueAllowNull(this._catalog);
  295. IPartCreatorImportDefinition partCreatorDefinition = definition as IPartCreatorImportDefinition;
  296. bool isExportFactory = false;
  297. if (partCreatorDefinition != null)
  298. {
  299. definition = partCreatorDefinition.ProductImportDefinition;
  300. isExportFactory = true;
  301. }
  302. CreationPolicy importPolicy = definition.GetRequiredCreationPolicy();
  303. List<Export> exports = new List<Export>();
  304. foreach (var partDefinitionAndExportDefinition in currentCatalog.GetExports(definition))
  305. {
  306. if (!this.IsRejected(partDefinitionAndExportDefinition.Item1, atomicComposition))
  307. {
  308. exports.Add(this.CreateExport(partDefinitionAndExportDefinition.Item1, partDefinitionAndExportDefinition.Item2, isExportFactory, importPolicy));
  309. }
  310. }
  311. return exports;
  312. }
  313. private Export CreateExport(ComposablePartDefinition partDefinition, ExportDefinition exportDefinition, bool isExportFactory, CreationPolicy importPolicy)
  314. {
  315. if (isExportFactory)
  316. {
  317. return new PartCreatorExport(this,
  318. partDefinition,
  319. exportDefinition);
  320. }
  321. else
  322. {
  323. return CatalogExport.CreateExport(this,
  324. partDefinition,
  325. exportDefinition,
  326. importPolicy);
  327. }
  328. }
  329. private void OnExportsChangingInternal(object sender, ExportsChangeEventArgs e)
  330. {
  331. UpdateRejections(e.AddedExports.Concat(e.RemovedExports), e.AtomicComposition);
  332. }
  333. private static ExportDefinition[] GetExportsFromPartDefinitions(IEnumerable<ComposablePartDefinition> partDefinitions)
  334. {
  335. List<ExportDefinition> exports = new List<ExportDefinition>();
  336. foreach (var partDefinition in partDefinitions)
  337. {
  338. foreach (var export in partDefinition.ExportDefinitions)
  339. {
  340. exports.Add(export);
  341. // While creating a PartCreatorExportDefinition for every changed definition may not be the most
  342. // efficient way to do this the PartCreatorExportDefinition is very efficient and doesn't do any
  343. // real work unless its metadata is pulled on. If this turns out to be a bottleneck then we
  344. // will need to start tracking all the PartCreator's we hand out and only send those which we
  345. // have handed out. In fact we could do the same thing for all the Exports if we wished but
  346. // that requires a cache management which we don't want to do at this point.
  347. exports.Add(new PartCreatorExportDefinition(export));
  348. }
  349. }
  350. return exports.ToArray();
  351. }
  352. [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
  353. private void OnCatalogChanging(object sender, ComposablePartCatalogChangeEventArgs e)
  354. {
  355. using (var atomicComposition = new AtomicComposition(e.AtomicComposition))
  356. {
  357. // Save the preview catalog to use in place of the original while handling
  358. // this event
  359. atomicComposition.SetValue(this._catalog,
  360. new CatalogChangeProxy(this._catalog, e.AddedDefinitions, e.RemovedDefinitions));
  361. IEnumerable<ExportDefinition> addedExports = GetExportsFromPartDefinitions(e.AddedDefinitions);
  362. IEnumerable<ExportDefinition> removedExports = GetExportsFromPartDefinitions(e.RemovedDefinitions);
  363. // Remove any parts based on eliminated definitions (in a atomicComposition-friendly
  364. // fashion)
  365. foreach (var definition in e.RemovedDefinitions)
  366. {
  367. CatalogPart removedPart = null;
  368. bool removed = false;
  369. using (this._lock.LockStateForRead())
  370. {
  371. removed = this._activatedParts.TryGetValue(definition, out removedPart);
  372. }
  373. if (removed)
  374. {
  375. var capturedDefinition = definition;
  376. this.ReleasePart(null, removedPart, atomicComposition);
  377. atomicComposition.AddCompleteActionAllowNull(() =>
  378. {
  379. using (this._lock.LockStateForWrite())
  380. {
  381. this._activatedParts.Remove(capturedDefinition);
  382. }
  383. });
  384. }
  385. }
  386. UpdateRejections(addedExports.ConcatAllowingNull(removedExports), atomicComposition);
  387. this.OnExportsChanging(
  388. new ExportsChangeEventArgs(addedExports, removedExports, atomicComposition));
  389. atomicComposition.AddCompleteAction(() => this.OnExportsChanged(
  390. new ExportsChangeEventArgs(addedExports, removedExports, null)));
  391. atomicComposition.Complete();
  392. }
  393. }
  394. private CatalogPart GetComposablePart(ComposablePartDefinition partDefinition, bool isSharedPart)
  395. {
  396. this.ThrowIfDisposed();
  397. this.EnsureRunning();
  398. CatalogPart catalogPart = null;
  399. if (isSharedPart)
  400. {
  401. catalogPart = this.GetSharedPart(partDefinition);
  402. }
  403. else
  404. {
  405. ComposablePart part = partDefinition.CreatePart();
  406. catalogPart = new CatalogPart(part);
  407. IDisposable disposablePart = part as IDisposable;
  408. if (disposablePart != null)
  409. {
  410. using (this._lock.LockStateForWrite())
  411. {
  412. this._partsToDispose.Add(disposablePart);
  413. }
  414. }
  415. }
  416. return catalogPart;
  417. }
  418. private CatalogPart GetSharedPart(ComposablePartDefinition partDefinition)
  419. {
  420. CatalogPart catalogPart = null;
  421. // look up the part
  422. using (this._lock.LockStateForRead())
  423. {
  424. if (this._activatedParts.TryGetValue(partDefinition, out catalogPart))
  425. {
  426. return catalogPart;
  427. }
  428. }
  429. // create a part outside of the lock
  430. ComposablePart newPart = partDefinition.CreatePart();
  431. IDisposable disposableNewPart = newPart as IDisposable;
  432. using (this._lock.LockStateForWrite())
  433. {
  434. // check if the part is still not there
  435. if (!this._activatedParts.TryGetValue(partDefinition, out catalogPart))
  436. {
  437. catalogPart = new CatalogPart(newPart);
  438. this._activatedParts.Add(partDefinition, catalogPart);
  439. if (disposableNewPart != null)
  440. {
  441. this._partsToDispose.Add(disposableNewPart);
  442. }
  443. // indiacate the the part has been added
  444. newPart = null;
  445. disposableNewPart = null;
  446. }
  447. }
  448. // if disposableNewPart != null, this means we have created a new instance of something disposable and not used it
  449. // Dispose of it now
  450. if (disposableNewPart != null)
  451. {
  452. disposableNewPart.Dispose();
  453. }
  454. return catalogPart;
  455. }
  456. private object GetExportedValue(CatalogPart part, ExportDefinition export, bool isSharedPart)
  457. {
  458. this.ThrowIfDisposed();
  459. this.EnsureRunning();
  460. Assumes.NotNull(part, export);
  461. // We don't protect against thread racing here, as "importsSatisfied" is merely an optimization
  462. // if two threads satisfy imports twice, the results is the same, just the perf hit is heavier.
  463. bool importsSatisfied = part.ImportsSatisfied;
  464. ImportEngine importEngine = importsSatisfied ? null : this._importEngine;
  465. object exportedValue = CompositionServices.GetExportedValueFromComposedPart(
  466. importEngine, part.Part, export);
  467. if (!importsSatisfied)
  468. {
  469. // and set "ImportsSatisfied" to true
  470. part.ImportsSatisfied = true;
  471. }
  472. // Only hold conditional references for recomposable non-shared parts because we are
  473. // already holding strong references to the shared parts.
  474. if (exportedValue != null && !isSharedPart && part.Part.IsRecomposable())
  475. {
  476. this.PreventPartCollection(exportedValue, part.Part);
  477. }
  478. return exportedValue;
  479. }
  480. private void ReleasePart(object exportedValue, CatalogPart catalogPart, AtomicComposition atomicComposition)
  481. {
  482. this.ThrowIfDisposed();
  483. this.EnsureRunning();
  484. Assumes.NotNull(catalogPart);
  485. this._importEngine.ReleaseImports(catalogPart.Part, atomicComposition);
  486. if (exportedValue != null)
  487. {
  488. atomicComposition.AddCompleteActionAllowNull(() =>
  489. {
  490. this.AllowPartCollection(exportedValue);
  491. });
  492. }
  493. IDisposable diposablePart = catalogPart.Part as IDisposable;
  494. if (diposablePart != null)
  495. {
  496. atomicComposition.AddCompleteActionAllowNull(() =>
  497. {
  498. bool removed = false;
  499. using (this._lock.LockStateForWrite())
  500. {
  501. removed = this._partsToDispose.Remove(diposablePart);
  502. }
  503. if (removed)
  504. {
  505. diposablePart.Dispose();
  506. }
  507. });
  508. }
  509. }
  510. private void PreventPartCollection(object exportedValue, ComposablePart part)
  511. {
  512. Assumes.NotNull(exportedValue, part);
  513. using (this._lock.LockStateForWrite())
  514. {
  515. List<ComposablePart> partList;
  516. ConditionalWeakTable<object, List<ComposablePart>> gcRoots = this._gcRoots;
  517. if (gcRoots == null)
  518. {
  519. gcRoots = new ConditionalWeakTable<object, List<ComposablePart>>();
  520. }
  521. if (!gcRoots.TryGetValue(exportedValue, out partList))
  522. {
  523. partList = new List<ComposablePart>();
  524. gcRoots.Add(exportedValue, partList);
  525. }
  526. partList.Add(part);
  527. if (this._gcRoots == null)
  528. {
  529. Thread.MemoryBarrier();
  530. this._gcRoots = gcRoots;
  531. }
  532. }
  533. }
  534. private void AllowPartCollection(object gcRoot)
  535. {
  536. if (this._gcRoots != null)
  537. {
  538. using (this._lock.LockStateForWrite())
  539. {
  540. this._gcRoots.Remove(gcRoot);
  541. }
  542. }
  543. }
  544. private bool IsRejected(ComposablePartDefinition definition, AtomicComposition atomicComposition)
  545. {
  546. // Check to see if we're currently working on the definition in question.
  547. // Recursive queries always answer optimistically, as if the definition hasn't
  548. // been rejected - because if it is we can discard all decisions that were based
  549. // on the faulty assumption in the first place.
  550. var forceRejectionTest = false;
  551. if (atomicComposition != null)
  552. {
  553. var atomicCompositionQuery = GetAtomicCompositionQuery(atomicComposition);
  554. AtomicCompositionQueryState state = atomicCompositionQuery(definition);
  555. switch (state)
  556. {
  557. case AtomicCompositionQueryState.TreatAsRejected:
  558. return true;
  559. case AtomicCompositionQueryState.TreatAsValidated:
  560. return false;
  561. case AtomicCompositionQueryState.NeedsTesting:
  562. forceRejectionTest = true;
  563. break;
  564. default:
  565. Assumes.IsTrue(state == AtomicCompositionQueryState.Unknown);
  566. // Need to do the work to determine the state
  567. break;
  568. }
  569. }
  570. if (!forceRejectionTest)
  571. {
  572. // Next, anything that has been activated is not rejected
  573. using (this._lock.LockStateForRead())
  574. {
  575. if (this._activatedParts.ContainsKey(definition))
  576. {
  577. return false;
  578. }
  579. // Last stop before doing the hard work: check a specific registry of rejected parts
  580. if (this._rejectedParts.Contains(definition))
  581. {
  582. return true;
  583. }
  584. }
  585. }
  586. // Determine whether or not the definition's imports can be satisfied
  587. return DetermineRejection(definition, atomicComposition);
  588. }
  589. private bool DetermineRejection(ComposablePartDefinition definition, AtomicComposition parentAtomicComposition)
  590. {
  591. ChangeRejectedException exception = null;
  592. using (var localAtomicComposition = new AtomicComposition(parentAtomicComposition))
  593. {
  594. // The part definition we're currently working on is treated optimistically
  595. // as if we know it hasn't been rejected. This handles recursion, and if we
  596. // later decide that it has been rejected we'll discard all nested progress so
  597. // all side-effects of the mistake are erased.
  598. //
  599. // Note that this means that recursive failures that would be detected by the
  600. // import engine are not discovered by rejection currently. Loops among
  601. // prerequisites, runaway import chains involving factories, and prerequisites
  602. // that cannot be fully satisfied still result in runtime errors. Doing
  603. // otherwise would be possible but potentially expensive - and could be a v2
  604. // improvement if deemed worthwhile.
  605. UpdateAtomicCompositionQuery(localAtomicComposition,
  606. def => definition.Equals(def), AtomicCompositionQueryState.TreatAsValidated);
  607. var newPart = definition.CreatePart();
  608. try
  609. {
  610. this._importEngine.PreviewImports(newPart, localAtomicComposition);
  611. // Reuse the partially-fleshed out part the next time we need a shared
  612. // instance to keep the expense of pre-validation to a minimum. Note that
  613. // _activatedParts holds references to both shared and non-shared parts.
  614. // The non-shared parts will only be used for rejection purposes only but
  615. // the shared parts will be handed out when requested via GetExports as
  616. // well as be used for rejection purposes.
  617. localAtomicComposition.AddCompleteActionAllowNull(() =>
  618. {
  619. using (this._lock.LockStateForWrite())
  620. {
  621. if (!this._activatedParts.ContainsKey(definition))
  622. {
  623. this._activatedParts.Add(definition, new CatalogPart(newPart));
  624. IDisposable newDisposablePart = newPart as IDisposable;
  625. if (newDisposablePart != null)
  626. {
  627. this._partsToDispose.Add(newDisposablePart);
  628. }
  629. }
  630. }
  631. });
  632. // Success! Complete any recursive work that was conditioned on this part's validation
  633. localAtomicComposition.Complete();
  634. return false;
  635. }
  636. catch (ChangeRejectedException ex)
  637. {
  638. exception = ex;
  639. }
  640. }
  641. // If we've reached this point then this part has been rejected so we need to
  642. // record the rejection in our parent composition or execute it immediately if
  643. // one doesn't exist.
  644. parentAtomicComposition.AddCompleteActionAllowNull(() =>
  645. {
  646. using (this._lock.LockStateForWrite())
  647. {
  648. this._rejectedParts.Add(definition);
  649. }
  650. CompositionTrace.PartDefinitionRejected(definition, exception);
  651. });
  652. if (parentAtomicComposition != null)
  653. {
  654. UpdateAtomicCompositionQuery(parentAtomicComposition,
  655. def => definition.Equals(def), AtomicCompositionQueryState.TreatAsRejected);
  656. }
  657. return true;
  658. }
  659. private void UpdateRejections(IEnumerable<ExportDefinition> changedExports, AtomicComposition atomicComposition)
  660. {
  661. using (var localAtomicComposition = new AtomicComposition(atomicComposition))
  662. {
  663. // Reconsider every part definition that has been previously
  664. // rejected to see if any of them can be added back.
  665. var affectedRejections = new HashSet<ComposablePartDefinition>();
  666. var atomicCompositionQuery = GetAtomicCompositionQuery(localAtomicComposition);
  667. ComposablePartDefinition[] rejectedParts;
  668. using (this._lock.LockStateForRead())
  669. {
  670. rejectedParts = this._rejectedParts.ToArray();
  671. }
  672. foreach (var definition in rejectedParts)
  673. {
  674. if (atomicCompositionQuery(definition) == AtomicCompositionQueryState.TreatAsValidated)
  675. {
  676. continue;
  677. }
  678. foreach (var import in definition.ImportDefinitions.Where(ImportEngine.IsRequiredImportForPreview))
  679. {
  680. if (changedExports.Any(export => import.IsConstraintSatisfiedBy(export)))
  681. {
  682. affectedRejections.Add(definition);
  683. break;
  684. }
  685. }
  686. }
  687. UpdateAtomicCompositionQuery(localAtomicComposition,
  688. def => affectedRejections.Contains(def), AtomicCompositionQueryState.NeedsTesting);
  689. // Determine if any of the resurrectable parts is now available so that we can
  690. // notify listeners of the exact changes to exports
  691. var resurrectedExports = new List<ExportDefinition>();
  692. foreach (var partDefinition in affectedRejections)
  693. {
  694. if (!IsRejected(partDefinition, localAtomicComposition))
  695. {
  696. // Notify listeners of the newly available exports and
  697. // prepare to remove the rejected part from the list of rejections
  698. resurrectedExports.AddRange(partDefinition.ExportDefinitions);
  699. // Capture the local so that the closure below refers to the current definition
  700. // in the loop and not the value of 'partDefinition' when the closure executes
  701. var capturedPartDefinition = partDefinition;
  702. localAtomicComposition.AddCompleteAction(() =>
  703. {
  704. using (this._lock.LockStateForWrite())
  705. {
  706. this._rejectedParts.Remove(capturedPartDefinition);
  707. }
  708. CompositionTrace.PartDefinitionResurrected(capturedPartDefinition);
  709. });
  710. }
  711. }
  712. // Notify anyone sourcing exports that the resurrected exports have appeared
  713. if (resurrectedExports.Any())
  714. {
  715. this.OnExportsChanging(
  716. new ExportsChangeEventArgs(resurrectedExports, new ExportDefinition[0], localAtomicComposition));
  717. localAtomicComposition.AddCompleteAction(() => this.OnExportsChanged(
  718. new ExportsChangeEventArgs(resurrectedExports, new ExportDefinition[0], null)));
  719. }
  720. localAtomicComposition.Complete();
  721. }
  722. }
  723. [DebuggerStepThrough]
  724. [ContractArgumentValidator]
  725. [SuppressMessage("Microsoft.Contracts", "CC1053", Justification = "Suppressing warning because this validator has no public contract")]
  726. private void ThrowIfDisposed()
  727. {
  728. if (this._isDisposed)
  729. {
  730. throw ExceptionBuilder.CreateObjectDisposed(this);
  731. }
  732. }
  733. /// <summary>
  734. /// EnsureCanRun must be called from within a lock.
  735. /// </summary>
  736. [DebuggerStepThrough]
  737. private void EnsureCanRun()
  738. {
  739. if ((this._sourceProvider == null) || (this._importEngine == null))
  740. {
  741. throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.ObjectMustBeInitialized, "SourceProvider")); // NOLOC
  742. }
  743. }
  744. [DebuggerStepThrough]
  745. private void EnsureRunning()
  746. {
  747. if (!this._isRunning)
  748. {
  749. using (this._lock.LockStateForWrite())
  750. {
  751. if (!this._isRunning)
  752. {
  753. this.EnsureCanRun();
  754. this._isRunning = true;
  755. }
  756. }
  757. }
  758. }
  759. /// <summary>
  760. /// EnsureCanSet<T> must be called from within a lock.
  761. /// </summary>
  762. /// <typeparam name="T"></typeparam>
  763. /// <param name="currentValue"></param>
  764. [DebuggerStepThrough]
  765. private void EnsureCanSet<T>(T currentValue)
  766. where T : class
  767. {
  768. if ((this._isRunning) || (currentValue != null))
  769. {
  770. throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.ObjectAlreadyInitialized));
  771. }
  772. }
  773. private Func<ComposablePartDefinition, AtomicCompositionQueryState> GetAtomicCompositionQuery(AtomicComposition atomicComposition)
  774. {
  775. Func<ComposablePartDefinition, AtomicCompositionQueryState> atomicCompositionQuery;
  776. atomicComposition.TryGetValue(this, out atomicCompositionQuery);
  777. if (atomicCompositionQuery == null)
  778. {
  779. return (definition) => AtomicCompositionQueryState.Unknown;
  780. }
  781. return atomicCompositionQuery;
  782. }
  783. private void UpdateAtomicCompositionQuery(
  784. AtomicComposition atomicComposition,
  785. Func<ComposablePartDefinition, bool> query,
  786. AtomicCompositionQueryState state)
  787. {
  788. var parentQuery = GetAtomicCompositionQuery(atomicComposition);
  789. Func<ComposablePartDefinition, AtomicCompositionQueryState> newQuery = definition =>
  790. {
  791. if (query(definition))
  792. {
  793. return state;
  794. }
  795. return parentQuery(definition);
  796. };
  797. atomicComposition.SetValue(this, newQuery);
  798. }
  799. private enum AtomicCompositionQueryState
  800. {
  801. Unknown,
  802. TreatAsRejected,
  803. TreatAsValidated,
  804. NeedsTesting
  805. };
  806. private class CatalogPart
  807. {
  808. private volatile bool _importsSatisfied = false;
  809. public CatalogPart(ComposablePart part)
  810. {
  811. this.Part = part;
  812. }
  813. public ComposablePart Part { get; private set; }
  814. public bool ImportsSatisfied
  815. {
  816. get
  817. {
  818. return this._importsSatisfied;
  819. }
  820. set
  821. {
  822. this._importsSatisfied = value;
  823. }
  824. }
  825. }
  826. }
  827. }