From 99e8bf05c7747fe7135608247ad1886a6ccc54a5 Mon Sep 17 00:00:00 2001 From: "ranxianglei.rxl" Date: Thu, 2 Jan 2025 12:04:30 +0800 Subject: [PATCH 1/6] [core][format] op format factory mv to cache . --- .../paimon/factories/FormatFactoryUtil.java | 67 ------------------- .../org/apache/paimon/format/FileFormat.java | 14 ++-- .../paimon/format/FileFormatFactory.java | 3 +- .../org.apache.paimon.factories.Factory | 1 + ...org.apache.paimon.format.FileFormatFactory | 16 ----- .../org.apache.paimon.factories.Factory | 5 ++ ...org.apache.paimon.format.FileFormatFactory | 18 ----- ...org.apache.paimon.format.FileFormatFactory | 2 +- 8 files changed, 19 insertions(+), 107 deletions(-) delete mode 100644 paimon-common/src/main/java/org/apache/paimon/factories/FormatFactoryUtil.java delete mode 100644 paimon-core/src/test/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory delete mode 100644 paimon-flink/paimon-flink-common/src/main/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory diff --git a/paimon-common/src/main/java/org/apache/paimon/factories/FormatFactoryUtil.java b/paimon-common/src/main/java/org/apache/paimon/factories/FormatFactoryUtil.java deleted file mode 100644 index 083b775461aa..000000000000 --- a/paimon-common/src/main/java/org/apache/paimon/factories/FormatFactoryUtil.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - -package org.apache.paimon.factories; - -import org.apache.paimon.format.FileFormatFactory; - -import org.apache.paimon.shade.caffeine2.com.github.benmanes.caffeine.cache.Cache; -import org.apache.paimon.shade.caffeine2.com.github.benmanes.caffeine.cache.Caffeine; - -import java.util.List; -import java.util.stream.Collectors; - -import static org.apache.paimon.factories.FactoryUtil.discoverFactories; - -/** Utility for working with {@link FileFormatFactory}s. */ -public class FormatFactoryUtil { - - private static final Cache> FACTORIES = - Caffeine.newBuilder().softValues().maximumSize(100).executor(Runnable::run).build(); - - /** Discovers a file format factory. */ - @SuppressWarnings("unchecked") - public static T discoverFactory( - ClassLoader classLoader, String identifier) { - final List foundFactories = getFactories(classLoader); - - final List matchingFactories = - foundFactories.stream() - .filter(f -> f.identifier().equals(identifier)) - .collect(Collectors.toList()); - - if (matchingFactories.isEmpty()) { - throw new FactoryException( - String.format( - "Could not find any factory for identifier '%s' that implements FileFormatFactory in the classpath.\n\n" - + "Available factory identifiers are:\n\n" - + "%s", - identifier, - foundFactories.stream() - .map(FileFormatFactory::identifier) - .collect(Collectors.joining("\n")))); - } - - return (T) matchingFactories.get(0); - } - - private static List getFactories(ClassLoader classLoader) { - return FACTORIES.get( - classLoader, s -> discoverFactories(classLoader, FileFormatFactory.class)); - } -} diff --git a/paimon-common/src/main/java/org/apache/paimon/format/FileFormat.java b/paimon-common/src/main/java/org/apache/paimon/format/FileFormat.java index e1391e7f5396..9ddb58257324 100644 --- a/paimon-common/src/main/java/org/apache/paimon/format/FileFormat.java +++ b/paimon-common/src/main/java/org/apache/paimon/format/FileFormat.java @@ -19,7 +19,7 @@ package org.apache.paimon.format; import org.apache.paimon.CoreOptions; -import org.apache.paimon.factories.FormatFactoryUtil; +import org.apache.paimon.factories.FactoryUtil; import org.apache.paimon.format.FileFormatFactory.FormatContext; import org.apache.paimon.options.Options; import org.apache.paimon.predicate.Predicate; @@ -88,9 +88,15 @@ public static FileFormat fromIdentifier(String identifier, Options options) { /** Create a {@link FileFormat} from format identifier and format options. */ public static FileFormat fromIdentifier(String identifier, FormatContext context) { - return FormatFactoryUtil.discoverFactory( - FileFormat.class.getClassLoader(), identifier.toLowerCase()) - .create(context); + if (identifier != null) { + identifier = identifier.toLowerCase(); + } + FileFormatFactory fileFormatFactory = + FactoryUtil.discoverFactory( + FileFormatFactory.class.getClassLoader(), + FileFormatFactory.class, + identifier); + return fileFormatFactory.create(context); } protected Options getIdentifierPrefixOptions(Options options) { diff --git a/paimon-common/src/main/java/org/apache/paimon/format/FileFormatFactory.java b/paimon-common/src/main/java/org/apache/paimon/format/FileFormatFactory.java index b726a84f24a2..d377cb2815c2 100644 --- a/paimon-common/src/main/java/org/apache/paimon/format/FileFormatFactory.java +++ b/paimon-common/src/main/java/org/apache/paimon/format/FileFormatFactory.java @@ -19,13 +19,14 @@ package org.apache.paimon.format; import org.apache.paimon.annotation.VisibleForTesting; +import org.apache.paimon.factories.Factory; import org.apache.paimon.options.MemorySize; import org.apache.paimon.options.Options; import javax.annotation.Nullable; /** Factory to create {@link FileFormat}. */ -public interface FileFormatFactory { +public interface FileFormatFactory extends Factory { String identifier(); diff --git a/paimon-core/src/test/resources/META-INF/services/org.apache.paimon.factories.Factory b/paimon-core/src/test/resources/META-INF/services/org.apache.paimon.factories.Factory index 7eb517ab9835..5e87f5e9ea2e 100644 --- a/paimon-core/src/test/resources/META-INF/services/org.apache.paimon.factories.Factory +++ b/paimon-core/src/test/resources/META-INF/services/org.apache.paimon.factories.Factory @@ -13,4 +13,5 @@ # See the License for the specific language governing permissions and # limitations under the License. +org.apache.paimon.format.FileStatsExtractingAvroFormatFactory org.apache.paimon.mergetree.compact.aggregate.TestCustomAggFactory \ No newline at end of file diff --git a/paimon-core/src/test/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory b/paimon-core/src/test/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory deleted file mode 100644 index 14386c45f21b..000000000000 --- a/paimon-core/src/test/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory +++ /dev/null @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You 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. - -org.apache.paimon.format.FileStatsExtractingAvroFormatFactory diff --git a/paimon-flink/paimon-flink-common/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory b/paimon-flink/paimon-flink-common/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory index 6251189560f6..4a9a46144798 100644 --- a/paimon-flink/paimon-flink-common/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory +++ b/paimon-flink/paimon-flink-common/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory @@ -82,3 +82,8 @@ org.apache.paimon.flink.procedure.MarkPartitionDoneProcedure org.apache.paimon.flink.procedure.CloneProcedure org.apache.paimon.flink.procedure.CompactManifestProcedure org.apache.paimon.flink.procedure.RefreshObjectTableProcedure + +### fileFormat factories +org.apache.paimon.flink.compact.changelog.format.CompactedChangelogReadOnlyFormat$OrcFactory +org.apache.paimon.flink.compact.changelog.format.CompactedChangelogReadOnlyFormat$ParquetFactory +org.apache.paimon.flink.compact.changelog.format.CompactedChangelogReadOnlyFormat$AvroFactory diff --git a/paimon-flink/paimon-flink-common/src/main/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory b/paimon-flink/paimon-flink-common/src/main/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory deleted file mode 100644 index 6e7553d5c668..000000000000 --- a/paimon-flink/paimon-flink-common/src/main/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory +++ /dev/null @@ -1,18 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You 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. - -org.apache.paimon.flink.compact.changelog.format.CompactedChangelogReadOnlyFormat$OrcFactory -org.apache.paimon.flink.compact.changelog.format.CompactedChangelogReadOnlyFormat$ParquetFactory -org.apache.paimon.flink.compact.changelog.format.CompactedChangelogReadOnlyFormat$AvroFactory diff --git a/paimon-format/src/main/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory b/paimon-format/src/main/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory index 7af6f79b3493..4aa1b23b3b7a 100644 --- a/paimon-format/src/main/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory +++ b/paimon-format/src/main/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory @@ -15,4 +15,4 @@ org.apache.paimon.format.avro.AvroFileFormatFactory org.apache.paimon.format.orc.OrcFileFormatFactory -org.apache.paimon.format.parquet.ParquetFileFormatFactory +org.apache.paimon.format.parquet.ParquetFileFormatFactory \ No newline at end of file From cb5ed40d4a082e840e57e5e7c092cb909978858d Mon Sep 17 00:00:00 2001 From: "ranxianglei.rxl" Date: Thu, 2 Jan 2025 12:35:34 +0800 Subject: [PATCH 2/6] [format] add paimon-format new factory --- ...rmat.FileFormatFactory => org.apache.paimon.factories.Factory} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename paimon-format/src/main/resources/META-INF/services/{org.apache.paimon.format.FileFormatFactory => org.apache.paimon.factories.Factory} (100%) diff --git a/paimon-format/src/main/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory b/paimon-format/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory similarity index 100% rename from paimon-format/src/main/resources/META-INF/services/org.apache.paimon.format.FileFormatFactory rename to paimon-format/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory From cb8504adc57e081ddbbd071535a59e3ce99bed53 Mon Sep 17 00:00:00 2001 From: "ranxianglei.rxl" Date: Thu, 2 Jan 2025 17:36:05 +0800 Subject: [PATCH 3/6] [common] Compatible with existing third-party factory implementations . --- .../factories/FactoryNotFoundException.java | 26 ++++++++++ .../apache/paimon/factories/FactoryUtil.java | 36 +++++++------ .../paimon/factories/FormatFactoryUtil.java | 50 +++++++++++++++++++ .../org/apache/paimon/format/FileFormat.java | 20 +++++--- 4 files changed, 110 insertions(+), 22 deletions(-) create mode 100644 paimon-common/src/main/java/org/apache/paimon/factories/FactoryNotFoundException.java create mode 100644 paimon-common/src/main/java/org/apache/paimon/factories/FormatFactoryUtil.java diff --git a/paimon-common/src/main/java/org/apache/paimon/factories/FactoryNotFoundException.java b/paimon-common/src/main/java/org/apache/paimon/factories/FactoryNotFoundException.java new file mode 100644 index 000000000000..003e1e3d7c3f --- /dev/null +++ b/paimon-common/src/main/java/org/apache/paimon/factories/FactoryNotFoundException.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.paimon.factories; + +/** Exception for factory not found. */ +public class FactoryNotFoundException extends FactoryException { + public FactoryNotFoundException(String message) { + super(message); + } +} diff --git a/paimon-common/src/main/java/org/apache/paimon/factories/FactoryUtil.java b/paimon-common/src/main/java/org/apache/paimon/factories/FactoryUtil.java index a492aeece7a8..d747c4e45f16 100644 --- a/paimon-common/src/main/java/org/apache/paimon/factories/FactoryUtil.java +++ b/paimon-common/src/main/java/org/apache/paimon/factories/FactoryUtil.java @@ -28,6 +28,7 @@ import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; +import java.util.function.Function; import java.util.stream.Collectors; /** Utility for working with {@link Factory}s. */ @@ -38,15 +39,12 @@ public class FactoryUtil { private static final Cache> FACTORIES = Caffeine.newBuilder().softValues().maximumSize(100).executor(Runnable::run).build(); - /** Discovers a factory using the given factory base class and identifier. */ - @SuppressWarnings("unchecked") public static T discoverFactory( ClassLoader classLoader, Class factoryClass, String identifier) { - final List factories = getFactories(classLoader); - - final List foundFactories = - factories.stream() + final List foundFactories = + getFactories(classLoader).stream() .filter(f -> factoryClass.isAssignableFrom(f.getClass())) + .map(factoryClass::cast) .collect(Collectors.toList()); if (foundFactories.isEmpty()) { @@ -56,31 +54,37 @@ public static T discoverFactory( factoryClass.getName())); } - final List matchingFactories = - foundFactories.stream() - .filter(f -> f.identifier().equals(identifier)) + return matchFactory(foundFactories, Factory::identifier, identifier); + } + + /** Discovers a factory using the given factory base class and identifier. */ + @SuppressWarnings("unchecked") + public static T matchFactory( + List factories, Function identifierFunction, String identifier) { + + final List matchingFactories = + factories.stream() + .filter(f -> identifierFunction.apply(f).equals(identifier)) .collect(Collectors.toList()); if (matchingFactories.isEmpty()) { - throw new FactoryException( + throw new FactoryNotFoundException( String.format( - "Could not find any factory for identifier '%s' that implements '%s' in the classpath.\n\n" + "Could not find any factory for identifier '%s' in the classpath.\n\n" + "Available factory identifiers are:\n\n" + "%s", identifier, - factoryClass.getName(), - foundFactories.stream() - .map(Factory::identifier) + factories.stream() + .map(identifierFunction) .collect(Collectors.joining("\n")))); } if (matchingFactories.size() > 1) { throw new FactoryException( String.format( - "Multiple factories for identifier '%s' that implement '%s' found in the classpath.\n\n" + "Multiple factories for identifier '%s' in the classpath.\n\n" + "Ambiguous factory classes are:\n\n" + "%s", identifier, - factoryClass.getName(), matchingFactories.stream() .map(f -> f.getClass().getName()) .sorted() diff --git a/paimon-common/src/main/java/org/apache/paimon/factories/FormatFactoryUtil.java b/paimon-common/src/main/java/org/apache/paimon/factories/FormatFactoryUtil.java new file mode 100644 index 000000000000..82d79bb4156b --- /dev/null +++ b/paimon-common/src/main/java/org/apache/paimon/factories/FormatFactoryUtil.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.paimon.factories; + +import org.apache.paimon.format.FileFormatFactory; + +import org.apache.paimon.shade.caffeine2.com.github.benmanes.caffeine.cache.Cache; +import org.apache.paimon.shade.caffeine2.com.github.benmanes.caffeine.cache.Caffeine; + +import java.util.List; + +import static org.apache.paimon.factories.FactoryUtil.discoverFactories; + +/** Utility for working with {@link FileFormatFactory}s. */ +public class FormatFactoryUtil { + + private static final Cache> FACTORIES = + Caffeine.newBuilder().softValues().maximumSize(100).executor(Runnable::run).build(); + + /** Discovers a file format factory. */ + @SuppressWarnings("unchecked") + public static T discoverFactory( + ClassLoader classLoader, String identifier) { + final List foundFactories = getFactories(classLoader); + + return (T) + FactoryUtil.matchFactory(foundFactories, FileFormatFactory::identifier, identifier); + } + + private static List getFactories(ClassLoader classLoader) { + return FACTORIES.get( + classLoader, s -> discoverFactories(classLoader, FileFormatFactory.class)); + } +} diff --git a/paimon-common/src/main/java/org/apache/paimon/format/FileFormat.java b/paimon-common/src/main/java/org/apache/paimon/format/FileFormat.java index 9ddb58257324..618a3a347374 100644 --- a/paimon-common/src/main/java/org/apache/paimon/format/FileFormat.java +++ b/paimon-common/src/main/java/org/apache/paimon/format/FileFormat.java @@ -19,7 +19,9 @@ package org.apache.paimon.format; import org.apache.paimon.CoreOptions; +import org.apache.paimon.factories.FactoryNotFoundException; import org.apache.paimon.factories.FactoryUtil; +import org.apache.paimon.factories.FormatFactoryUtil; import org.apache.paimon.format.FileFormatFactory.FormatContext; import org.apache.paimon.options.Options; import org.apache.paimon.predicate.Predicate; @@ -91,12 +93,18 @@ public static FileFormat fromIdentifier(String identifier, FormatContext context if (identifier != null) { identifier = identifier.toLowerCase(); } - FileFormatFactory fileFormatFactory = - FactoryUtil.discoverFactory( - FileFormatFactory.class.getClassLoader(), - FileFormatFactory.class, - identifier); - return fileFormatFactory.create(context); + try { + FileFormatFactory fileFormatFactory = + FactoryUtil.discoverFactory( + FileFormatFactory.class.getClassLoader(), + FileFormatFactory.class, + identifier); + return fileFormatFactory.create(context); + } catch (FactoryNotFoundException e) { + // Compatible with existing third-party factory implementations . + return FormatFactoryUtil.discoverFactory(FileFormat.class.getClassLoader(), identifier) + .create(context); + } } protected Options getIdentifierPrefixOptions(Options options) { From ca844ef447df137197193ec168a5a795a3c30987 Mon Sep 17 00:00:00 2001 From: "ranxianglei.rxl" Date: Thu, 2 Jan 2025 17:48:56 +0800 Subject: [PATCH 4/6] [common] matchFactory make sure T is Factory . --- .../src/main/java/org/apache/paimon/factories/FactoryUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paimon-common/src/main/java/org/apache/paimon/factories/FactoryUtil.java b/paimon-common/src/main/java/org/apache/paimon/factories/FactoryUtil.java index d747c4e45f16..fc2e5e24756d 100644 --- a/paimon-common/src/main/java/org/apache/paimon/factories/FactoryUtil.java +++ b/paimon-common/src/main/java/org/apache/paimon/factories/FactoryUtil.java @@ -59,7 +59,7 @@ public static T discoverFactory( /** Discovers a factory using the given factory base class and identifier. */ @SuppressWarnings("unchecked") - public static T matchFactory( + public static T matchFactory( List factories, Function identifierFunction, String identifier) { final List matchingFactories = From dea8d0ca3d1b16cb7634f0925673a8aee20cc527 Mon Sep 17 00:00:00 2001 From: "ranxianglei.rxl" Date: Thu, 2 Jan 2025 18:10:22 +0800 Subject: [PATCH 5/6] [common] FactoryUtilTest.java --- .../java/org/apache/paimon/factories/FactoryUtilTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paimon-common/src/test/java/org/apache/paimon/factories/FactoryUtilTest.java b/paimon-common/src/test/java/org/apache/paimon/factories/FactoryUtilTest.java index 7ade0a851fea..23583361d137 100644 --- a/paimon-common/src/test/java/org/apache/paimon/factories/FactoryUtilTest.java +++ b/paimon-common/src/test/java/org/apache/paimon/factories/FactoryUtilTest.java @@ -40,9 +40,9 @@ public void testDiscoverFactory() { Thread.currentThread().getContextClassLoader(), DummyFactory.class, "non-exist-factory")) - .isInstanceOf(FactoryException.class) + .isInstanceOf(FactoryNotFoundException.class) .hasMessageContaining( - "Could not find any factory for identifier '%s' that implements '%s' in the classpath.", - "non-exist-factory", DummyFactory.class.getName()); + "Could not find any factory for identifier '%s' in the classpath.", + "non-exist-factory", DummyFactory.class.getName(), ""); } } From ac3874c59b9741c14ed307239a854b27365a004b Mon Sep 17 00:00:00 2001 From: "ranxianglei.rxl" Date: Thu, 2 Jan 2025 18:12:40 +0800 Subject: [PATCH 6/6] [common][test] no need use FactoryUtilTest.java --- .../java/org/apache/paimon/factories/FactoryUtilTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/paimon-common/src/test/java/org/apache/paimon/factories/FactoryUtilTest.java b/paimon-common/src/test/java/org/apache/paimon/factories/FactoryUtilTest.java index 23583361d137..cb2f219cff61 100644 --- a/paimon-common/src/test/java/org/apache/paimon/factories/FactoryUtilTest.java +++ b/paimon-common/src/test/java/org/apache/paimon/factories/FactoryUtilTest.java @@ -40,9 +40,6 @@ public void testDiscoverFactory() { Thread.currentThread().getContextClassLoader(), DummyFactory.class, "non-exist-factory")) - .isInstanceOf(FactoryNotFoundException.class) - .hasMessageContaining( - "Could not find any factory for identifier '%s' in the classpath.", - "non-exist-factory", DummyFactory.class.getName(), ""); + .isInstanceOf(FactoryNotFoundException.class); } }