1 // -----------------------------------------------------------------------
\r
2 // Copyright (c) Microsoft Corporation. All rights reserved.
\r
3 // -----------------------------------------------------------------------
\r
5 using System.Collections.Generic;
\r
6 using System.Diagnostics;
\r
7 using System.ComponentModel.Composition.Diagnostics;
\r
8 using System.ComponentModel.Composition.Factories;
\r
9 using System.ComponentModel.Composition.Hosting;
\r
10 using System.ComponentModel.Composition.Primitives;
\r
12 using System.UnitTesting;
\r
13 using Microsoft.VisualStudio.TestTools.UnitTesting;
\r
15 namespace System.ComponentModel.Composition
\r
18 public class ComposablePartCatalogExportProviderTests
\r
21 public void Constructor_NullAsCatalogArgument_ShouldThrowArgumentNull()
\r
23 ExceptionAssert.ThrowsArgument<ArgumentNullException>("catalog", () =>
\r
25 new CatalogExportProvider((ComposablePartCatalog)null);
\r
30 public void Constructor_ValueAsCatalogArgument_ShouldSetCatalogPropertyToEmpty()
\r
32 var expectations = Expectations.GetCatalogs();
\r
34 foreach (var e in expectations)
\r
36 var provider = new CatalogExportProvider(e);
\r
38 Assert.AreSame(e, provider.Catalog);
\r
43 public void Catalog_WhenDisposed_ShouldThrowObjectDisposed()
\r
45 var provider = CreateCatalogExportProvider();
\r
48 ExceptionAssert.ThrowsDisposed(provider, () =>
\r
50 var catalog = provider.Catalog;
\r
55 public void SourceProvider_NullAsValueArgument_ShouldThrowArgumentNull()
\r
57 var provider = CreateCatalogExportProvider();
\r
59 ExceptionAssert.ThrowsArgument<ArgumentNullException>("value", () =>
\r
61 provider.SourceProvider = null;
\r
66 public void GetExports_WhenRejectedDefinitionRequiredImportIsAdded_ShouldBeResurrected()
\r
68 var part = PartFactory.CreateImporterExporter("Export", "Import");
\r
70 var provider = CreateCatalogExportProvider(part);
\r
71 var sourceProvider = ExportProviderFactory.CreateRecomposable();
\r
72 provider.SourceProvider = sourceProvider;
\r
74 var exports = provider.GetExports<object>("Export");
\r
76 EnumerableAssert.IsEmpty(exports, "definition should have been rejected.");
\r
78 // Resurrect the definition
\r
79 sourceProvider.AddExport("Import", new object());
\r
81 exports = provider.GetExports<object>("Export");
\r
83 Assert.AreEqual(1, exports.Count(), "definition should have been resurrected.");
\r
87 public void GetExports_WhenMultipleRejectedDefinitionsRequiredImportsAreAdded_ShouldBeResurrected()
\r
89 var part1 = PartFactory.CreateImporterExporter("Export", "Import");
\r
90 var part2 = PartFactory.CreateImporterExporter("Export", "Import");
\r
92 var provider = CreateCatalogExportProvider(part1, part2);
\r
93 var sourceProvider = ExportProviderFactory.CreateRecomposable();
\r
94 provider.SourceProvider = sourceProvider;
\r
96 var exports = provider.GetExports<object>("Export");
\r
98 EnumerableAssert.IsEmpty(exports, "definition1 and definition2 should have been rejected.");
\r
100 // Resurrect both definitions
\r
101 sourceProvider.AddExport("Import", new object());
\r
103 exports = provider.GetExports<object>("Export");
\r
105 Assert.AreEqual(2, exports.Count(), "definition1 and definition2 should have been resurrected.");
\r
110 public void GetExports_AfterResurrectedDefinitionHasBeenRemovedAndReaddedToCatalog_ShouldNotBeTreatedAsRejected()
\r
112 var definition1 = PartDefinitionFactory.Create(PartFactory.CreateImporterExporter("Export", "Import"));
\r
113 var definition2 = PartDefinitionFactory.Create(PartFactory.CreateImporterExporter("Export", "Import"));
\r
114 var catalog = CatalogFactory.CreateMutable(definition1, definition2);
\r
116 var provider = CreateCatalogExportProvider(catalog);
\r
117 var sourceProvider = ExportProviderFactory.CreateRecomposable();
\r
118 provider.SourceProvider = sourceProvider;
\r
120 var exports = provider.GetExports<object>("Export");
\r
122 EnumerableAssert.IsEmpty(exports, "definition1 and definition2 should have been rejected.");
\r
124 // Resurrect both definitions
\r
125 sourceProvider.AddExport("Import", new object());
\r
127 exports = provider.GetExports<object>("Export");
\r
129 Assert.AreEqual(2, exports.Count(), "definition1 and definition2 should have been resurrected.");
\r
131 catalog.RemoveDefinition(definition1);
\r
133 exports = provider.GetExports<object>("Export");
\r
134 Assert.AreEqual(1, exports.Count(), "definition1 should have been removed.");
\r
136 catalog.AddDefinition(definition1);
\r
138 exports = provider.GetExports<object>("Export");
\r
140 Assert.AreEqual(2, exports.Count(), "definition1 and definition2 should be both present.");
\r
146 public void GetExports_WhenDefinitionIsRejected_ShouldTraceWarning()
\r
148 using (TraceContext context = new TraceContext(SourceLevels.Warning))
\r
150 var part = PartFactory.CreateImporterExporter("Export", "Import");
\r
151 var provider = CreateCatalogExportProvider(part);
\r
152 provider.SourceProvider = ExportProviderFactory.CreateRecomposable();
\r
154 ExceptionAssert.Throws<ImportCardinalityMismatchException>(() =>
\r
156 provider.GetExport<object>("Export");
\r
160 Assert.IsNotNull(context.LastTraceEvent);
\r
161 Assert.AreEqual(context.LastTraceEvent.EventType, TraceEventType.Warning);
\r
162 Assert.AreEqual(context.LastTraceEvent.Id, TraceId.Rejection_DefinitionRejected);
\r
167 public void GetExports_WhenDefinitionIsResurrected_ShouldTraceInformation()
\r
169 using (TraceContext context = new TraceContext(SourceLevels.Information))
\r
171 var part = PartFactory.CreateImporterExporter("Export", "Import");
\r
172 var sourceProvider = ExportProviderFactory.CreateRecomposable();
\r
173 var provider = CreateCatalogExportProvider(part);
\r
174 provider.SourceProvider = sourceProvider;
\r
176 ExceptionAssert.Throws<ImportCardinalityMismatchException>(() =>
\r
178 provider.GetExport<object>("Export");
\r
181 // Add the required export to the source provider 'resurrect' the part
\r
182 sourceProvider.AddExport("Import", "Value");
\r
184 provider.GetExport<object>("Export");
\r
186 Assert.IsNotNull(context.LastTraceEvent);
\r
187 Assert.AreEqual(context.LastTraceEvent.EventType, TraceEventType.Information);
\r
188 Assert.AreEqual(context.LastTraceEvent.Id, TraceId.Rejection_DefinitionResurrected);
\r
193 public void GetExports_WhenDefinitionsAreResurrected_ShouldTraceInformation()
\r
195 using (TraceContext context = new TraceContext(SourceLevels.Information))
\r
197 var part1 = PartFactory.CreateImporterExporter("Export", "Import");
\r
198 var part2 = PartFactory.CreateImporterExporter("Export", "Import");
\r
200 var sourceProvider = ExportProviderFactory.CreateRecomposable();
\r
201 var provider = CreateCatalogExportProvider(part1, part2);
\r
202 provider.SourceProvider = sourceProvider;
\r
204 EnumerableAssert.IsEmpty(provider.GetExports<object>("Export"));
\r
206 // Add the required export to the source provider 'resurrect' the part
\r
207 sourceProvider.AddExport("Import", "Value");
\r
209 provider.GetExports<object>("Export");
\r
211 Assert.AreEqual(4, context.TraceEvents.Count); // 2 for rejection, 2 for resurrection
\r
212 Assert.AreEqual(context.TraceEvents[2].EventType, TraceEventType.Information);
\r
213 Assert.AreEqual(context.TraceEvents[3].EventType, TraceEventType.Information);
\r
214 Assert.AreEqual(context.TraceEvents[2].Id, TraceId.Rejection_DefinitionResurrected);
\r
215 Assert.AreEqual(context.TraceEvents[3].Id, TraceId.Rejection_DefinitionResurrected);
\r
221 [TestProperty("Type", "Integration")]
\r
222 public void BasicTest()
\r
224 var catalog = CatalogFactory.CreateDefaultAttributed();
\r
225 var catalogExportProvider = new CatalogExportProvider(catalog);
\r
226 catalogExportProvider.SourceProvider = catalogExportProvider;
\r
227 var testName = AttributedModelServices.GetContractName(typeof(CatalogComponentTest));
\r
228 var testNameNonComponent = AttributedModelServices.GetContractName(typeof(CatalogComponentTestNonComponentPart));
\r
229 var testInterfaceName = AttributedModelServices.GetContractName(typeof(ICatalogComponentTest));
\r
231 Assert.AreEqual(1, catalogExportProvider.GetExports(ImportFromContract(testName)).Count());
\r
232 Assert.AreEqual(0, catalogExportProvider.GetExports(ImportFromContract(testNameNonComponent)).Count());
\r
234 var exports = catalogExportProvider.GetExports(ImportFromContract(testInterfaceName));
\r
235 Assert.AreEqual(2, exports.Count(), "There should be 2 of them");
\r
237 foreach (var i in exports)
\r
238 Assert.IsNotNull(i.Value, "Should get a value");
\r
243 [TestProperty("Type", "Integration")]
\r
244 public void BasicTestWithRequiredMetadata_NoTypeConstraint()
\r
246 var catalog = CatalogFactory.CreateDefaultAttributed();
\r
247 var catalogExportProvider = new CatalogExportProvider(catalog);
\r
248 catalogExportProvider.SourceProvider = catalogExportProvider;
\r
250 Assert.AreEqual(0, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithNoFoo", new string[] { "Foo" }, new Type[] {typeof(object)})).Count());
\r
252 Assert.AreEqual(1, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo" }, new Type[] { typeof(object) })).Count());
\r
253 Assert.AreEqual(0, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo", "Bar" }, new Type[] { typeof(object), typeof(object) })).Count());
\r
255 Assert.AreEqual(0, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithNoFoo", new string[] { "Foo" }, new Type[] { typeof(object) })).Count());
\r
256 Assert.AreEqual(1, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo" }, new Type[] { typeof(object) })).Count());
\r
260 [TestProperty("Type", "Integration")]
\r
261 public void BasicTestWithRequiredMetadata_TypeConstraint()
\r
263 var catalog = CatalogFactory.CreateDefaultAttributed();
\r
264 var catalogExportProvider = new CatalogExportProvider(catalog);
\r
265 catalogExportProvider.SourceProvider = catalogExportProvider;
\r
267 Assert.AreEqual(0, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithNoFoo", new string[] { "Foo" }, new Type[] { typeof(string) })).Count());
\r
269 Assert.AreEqual(1, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo" }, new Type[] { typeof(string) })).Count());
\r
270 Assert.AreEqual(0, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo", "Bar" }, new Type[] { typeof(string), typeof(string) })).Count());
\r
272 Assert.AreEqual(0, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithNoFoo", new string[] { "Foo" }, new Type[] { typeof(string) })).Count());
\r
273 Assert.AreEqual(1, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo" }, new Type[] { typeof(string) })).Count());
\r
278 [TestProperty("Type", "Integration")]
\r
279 public void BasicTestWithRequiredMetadata_WrongTypeConstraint()
\r
281 var catalog = CatalogFactory.CreateDefaultAttributed();
\r
282 var catalogExportProvider = new CatalogExportProvider(catalog);
\r
283 catalogExportProvider.SourceProvider = catalogExportProvider;
\r
285 Assert.AreEqual(0, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithNoFoo", new string[] { "Foo" }, new Type[] { typeof(int) })).Count());
\r
287 Assert.AreEqual(0, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo" }, new Type[] { typeof(int) })).Count());
\r
288 Assert.AreEqual(0, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo", "Bar" }, new Type[] { typeof(int), typeof(int) })).Count());
\r
290 Assert.AreEqual(0, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithNoFoo", new string[] { "Foo" }, new Type[] { typeof(int) })).Count());
\r
291 Assert.AreEqual(0, catalogExportProvider.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo" }, new Type[] { typeof(int) })).Count());
\r
296 [TestProperty("Type", "Integration")]
\r
297 public void ComponentCatalogResolverGetStaticExport()
\r
299 var catalog = CatalogFactory.CreateDefaultAttributed();
\r
300 var catalogExportProvider = new CatalogExportProvider(catalog);
\r
301 catalogExportProvider.SourceProvider = catalogExportProvider;
\r
303 var exports = catalogExportProvider.GetExports(ImportFromContract("StaticString"));
\r
304 Assert.AreEqual(1, exports.Count());
\r
305 Assert.AreEqual("StaticString", exports.First().Value);
\r
309 [TestProperty("Type", "Integration")]
\r
310 public void ComponentCatalogResolverComponentCatalogExportReference()
\r
312 var catalog = CatalogFactory.CreateDefaultAttributed();
\r
313 var catalogExportProvider = new CatalogExportProvider(catalog);
\r
314 catalogExportProvider.SourceProvider = catalogExportProvider;
\r
316 var exports = catalogExportProvider.GetExports(ImportFromContract(AttributedModelServices.GetContractName(typeof(MyExporterWithValidMetadata))));
\r
318 Assert.AreEqual(1, exports.Count());
\r
320 var export = exports.First();
\r
321 Assert.AreEqual("world", export.Metadata["hello"]);
\r
323 Assert.IsInstanceOfType(export.Value, typeof(MyExporterWithValidMetadata));
\r
327 [TestProperty("Type", "Integration")]
\r
328 public void ValueTypeFromCatalog()
\r
330 var catalog = CatalogFactory.CreateDefaultAttributed();
\r
331 var container = new CompositionContainer(catalog);
\r
332 int singletonResult = container.GetExportedValue<int>("{AssemblyCatalogResolver}SingletonValueType");
\r
333 Assert.AreEqual(17, singletonResult, "expecting value type resolved from catalog");
\r
334 int factoryResult = container.GetExportedValue<int>("{AssemblyCatalogResolver}FactoryValueType");
\r
335 Assert.AreEqual(18, factoryResult, "expecting value type resolved from catalog");
\r
339 [PartCreationPolicy(CreationPolicy.Any)]
\r
340 public class CreationPolicyAny
\r
346 public void CreationPolicyAny_MultipleCallsReturnSameInstance()
\r
348 var catalog = CatalogFactory.CreateAttributed(typeof (CreationPolicyAny));
\r
349 var provider = new CatalogExportProvider(catalog);
\r
350 provider.SourceProvider = ContainerFactory.Create();
\r
352 var export = provider.GetExportedValue<CreationPolicyAny>();
\r
354 for (int i = 0; i < 5; i++) // 5 is arbitrarily chosen
\r
356 var export1 = provider.GetExportedValue<CreationPolicyAny>();
\r
358 Assert.AreEqual(export, export1);
\r
363 [PartCreationPolicy(CreationPolicy.Shared)]
\r
364 public class CreationPolicyShared
\r
370 public void CreationPolicyShared_MultipleCallsReturnSameInstance()
\r
372 var catalog = CatalogFactory.CreateAttributed(typeof(CreationPolicyShared));
\r
373 var provider = new CatalogExportProvider(catalog);
\r
374 provider.SourceProvider = ContainerFactory.Create();
\r
376 var export = provider.GetExportedValue<CreationPolicyShared>();
\r
378 for (int i = 0; i < 5; i++) // 5 is arbitrarily chosen
\r
380 var export1 = provider.GetExportedValue<CreationPolicyShared>();
\r
382 Assert.AreEqual(export, export1);
\r
387 [PartCreationPolicy(CreationPolicy.NonShared)]
\r
388 public class CreationPolicyNonShared
\r
394 public void CreationPolicyNonShared_MultipleCallsReturnsDifferentInstances()
\r
396 var catalog = CatalogFactory.CreateAttributed(typeof(CreationPolicyNonShared));
\r
397 var provider = new CatalogExportProvider(catalog);
\r
398 provider.SourceProvider = ContainerFactory.Create();
\r
400 List<CreationPolicyNonShared> list = new List<CreationPolicyNonShared>();
\r
401 var export = provider.GetExportedValue<CreationPolicyNonShared>();
\r
404 for (int i = 0; i < 5; i++) // 5 is arbitrarily chosen
\r
406 export = provider.GetExportedValue<CreationPolicyNonShared>();
\r
408 CollectionAssert.DoesNotContain(list, export);
\r
415 public void GetExports_NoSourceProvider_ShouldThrowInvalidOperation()
\r
417 var catalog = CatalogFactory.CreateAttributed();
\r
418 var provider = new CatalogExportProvider(catalog);
\r
420 ExceptionAssert.Throws<InvalidOperationException>(() =>
\r
421 provider.GetExports(ImportFromContract("Foo")));
\r
425 [TestProperty("Type", "Integration")]
\r
428 public void Recomposition_PartDefWithRecomposableImportIsRemoved_ExportsMatchingImportChanged_ShouldNotBeRecomposed()
\r
430 string dependencyContractName = "dependency";
\r
431 var exportValue = new object();
\r
433 var exporterPart = PartFactory.CreateExporter(dependencyContractName, exportValue);
\r
434 var importerPart = PartFactory.CreateImporter(dependencyContractName, true);
\r
436 var exporterCatalog = CatalogFactory.Create(exporterPart);
\r
437 var importerCatalog = CatalogFactory.Create(importerPart);
\r
439 var aggregateCatalog = CatalogFactory.CreateAggregateCatalog(importerCatalog, exporterCatalog);
\r
441 var provider = new CatalogExportProvider(aggregateCatalog);
\r
442 provider.SourceProvider = provider;
\r
444 var exports = provider.GetExports(importerPart.ImportDefinitions.Single());
\r
445 Assert.AreEqual(exportValue, importerPart.Value, "Importer was not composed");
\r
447 aggregateCatalog.Catalogs.Remove(importerCatalog);
\r
448 aggregateCatalog.Catalogs.Remove(exporterCatalog);
\r
450 Assert.AreEqual(exportValue, importerPart.Value, "Importer was unexpectedly recomposed");
\r
454 [TestProperty("Type", "Integration")]
\r
457 public void Recomposition_PartDefWithNonRecomposableImportIsRemoved_ExportsMatchingImportChanged_ShouldNotBeRejected()
\r
459 string dependencyContractName = "dependency";
\r
460 var exportValue = new object();
\r
462 var exporterPart = PartFactory.CreateExporter(dependencyContractName, exportValue);
\r
463 var importerPart = PartFactory.CreateImporter(dependencyContractName, false);
\r
465 var exporterCatalog = CatalogFactory.Create(exporterPart);
\r
466 var importerCatalog = CatalogFactory.Create(importerPart);
\r
468 var aggregateCatalog = CatalogFactory.CreateAggregateCatalog(importerCatalog, exporterCatalog);
\r
470 var provider = new CatalogExportProvider(aggregateCatalog);
\r
471 provider.SourceProvider = provider;
\r
473 var exports = provider.GetExports(importerPart.ImportDefinitions.Single());
\r
474 Assert.AreEqual(exportValue, importerPart.Value, "Importer was not composed");
\r
476 aggregateCatalog.Catalogs.Remove(importerCatalog);
\r
477 aggregateCatalog.Catalogs.Remove(exporterCatalog);
\r
479 Assert.AreEqual(exportValue, importerPart.Value, "Importer was unexpectedly recomposed");
\r
483 public void CanBeCollectedAfterDispose()
\r
485 AggregateExportProvider sourceExportProvider = new AggregateExportProvider();
\r
486 var catalog = new AggregateCatalog(CatalogFactory.CreateDefaultAttributed());
\r
487 var catalogExportProvider = new CatalogExportProvider(catalog);
\r
488 catalogExportProvider.SourceProvider = sourceExportProvider;
\r
490 WeakReference weakCatalogExportProvider = new WeakReference(catalogExportProvider);
\r
491 catalogExportProvider.Dispose();
\r
492 catalogExportProvider = null;
\r
495 GC.WaitForPendingFinalizers();
\r
497 Assert.IsFalse(weakCatalogExportProvider.IsAlive);
\r
499 GC.KeepAlive(sourceExportProvider);
\r
500 GC.KeepAlive(catalog);
\r
504 public void RemovingAndReAddingMultipleDefinitionsFromCatalog()
\r
506 var fixedParts = new TypeCatalog(typeof(RootMultipleImporter), typeof(ExportedService));
\r
507 var changingParts = new TypeCatalog(typeof(Exporter1), typeof(Exporter2));
\r
508 var catalog = new AggregateCatalog();
\r
509 catalog.Catalogs.Add(fixedParts);
\r
510 catalog.Catalogs.Add(changingParts);
\r
511 var catalogExportProvider = new CatalogExportProvider(catalog);
\r
512 catalogExportProvider.SourceProvider = catalogExportProvider;
\r
514 var root = catalogExportProvider.GetExport<RootMultipleImporter>().Value;
\r
515 Assert.AreEqual(2, root.Imports.Length);
\r
517 catalog.Catalogs.Remove(changingParts);
\r
518 Assert.AreEqual(0, root.Imports.Length);
\r
520 catalog.Catalogs.Add(changingParts);
\r
521 Assert.AreEqual(2, root.Imports.Length);
\r
525 public class RootMultipleImporter
\r
527 [ImportMany(AllowRecomposition = true)]
\r
528 public IExportedInterface[] Imports { get; set; }
\r
530 public interface IExportedInterface
\r
533 [Export(typeof(IExportedInterface))]
\r
534 public class Exporter1 : IExportedInterface
\r
537 public ExportedService Service { get; set; }
\r
539 [Export(typeof(IExportedInterface))]
\r
540 public class Exporter2 : IExportedInterface
\r
543 public ExportedService Service { get; set; }
\r
546 public class ExportedService
\r
552 private static ImportDefinition ImportFromContract(string contractName)
\r
554 return ImportDefinitionFactory.CreateDefault(contractName,
\r
556 ImportCardinality.ZeroOrMore,
\r
561 private static ImportDefinition ImportFromContractAndMetadata(string contractName, string[] metadataKeys, Type[] metadataValues)
\r
563 Assert.AreEqual(metadataKeys.Length, metadataValues.Length);
\r
564 Dictionary<string, Type> requiredMetadata = new Dictionary<string, Type>();
\r
565 for (int i = 0; i < metadataKeys.Length; i++)
\r
567 requiredMetadata.Add(metadataKeys[i], metadataValues[i]);
\r
570 return new ContractBasedImportDefinition(contractName,
\r
573 ImportCardinality.ZeroOrMore,
\r
576 CreationPolicy.Any);
\r
579 private static CatalogExportProvider CreateCatalogExportProvider()
\r
581 return CreateCatalogExportProvider(CatalogFactory.Create());
\r
584 private static CatalogExportProvider CreateCatalogExportProvider(params ComposablePartDefinition[] definitions)
\r
586 return CreateCatalogExportProvider(CatalogFactory.Create(definitions));
\r
589 private static CatalogExportProvider CreateCatalogExportProvider(params ComposablePart[] parts)
\r
591 return CreateCatalogExportProvider(CatalogFactory.Create(parts));
\r
594 private static CatalogExportProvider CreateCatalogExportProvider(ComposablePartCatalog catalog)
\r
596 return new CatalogExportProvider(catalog);
\r