diff --git a/paimon-common/src/main/java/org/apache/paimon/CoreOptions.java b/paimon-common/src/main/java/org/apache/paimon/CoreOptions.java index 6e1e9bba076b..a6248da404fe 100644 --- a/paimon-common/src/main/java/org/apache/paimon/CoreOptions.java +++ b/paimon-common/src/main/java/org/apache/paimon/CoreOptions.java @@ -1583,6 +1583,10 @@ public FileFormat fileFormat() { return createFileFormat(options, FILE_FORMAT); } + public String fileFormatString() { + return normalizeFileFormat(options.get(FILE_FORMAT)); + } + public FileFormat manifestFormat() { return createFileFormat(options, MANIFEST_FORMAT); } 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 1213168e16ae..a492aeece7a8 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 @@ -101,14 +101,21 @@ public static List discoverIdentifiers( } private static List getFactories(ClassLoader classLoader) { - return FACTORIES.get(classLoader, FactoryUtil::discoverFactories); + return FACTORIES.get(classLoader, s -> discoverFactories(classLoader, Factory.class)); } - private static List discoverFactories(ClassLoader classLoader) { - final Iterator serviceLoaderIterator = - ServiceLoader.load(Factory.class, classLoader).iterator(); - - final List loadResults = new ArrayList<>(); + /** + * Discover factories. + * + * @param classLoader the class loader + * @param klass the klass + * @param the type of the factory + * @return the list of factories + */ + public static List discoverFactories(ClassLoader classLoader, Class klass) { + final Iterator serviceLoaderIterator = ServiceLoader.load(klass, classLoader).iterator(); + + final List loadResults = new ArrayList<>(); while (true) { try { // error handling should also be applied to the hasNext() call because service 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..083b775461aa --- /dev/null +++ b/paimon-common/src/main/java/org/apache/paimon/factories/FormatFactoryUtil.java @@ -0,0 +1,67 @@ +/* + * 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 9d138d800680..e1391e7f5396 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,6 +19,7 @@ package org.apache.paimon.format; import org.apache.paimon.CoreOptions; +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; @@ -32,7 +33,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.ServiceLoader; /** * Factory class which creates reader and writer factories for specific file format. @@ -88,26 +88,9 @@ 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 fromIdentifier(identifier, context, FileFormat.class.getClassLoader()) - .orElseThrow( - () -> - new RuntimeException( - String.format( - "Could not find a FileFormatFactory implementation class for %s format", - identifier))); - } - - private static Optional fromIdentifier( - String formatIdentifier, FormatContext context, ClassLoader classLoader) { - ServiceLoader serviceLoader = - ServiceLoader.load(FileFormatFactory.class, classLoader); - for (FileFormatFactory factory : serviceLoader) { - if (factory.identifier().equals(formatIdentifier.toLowerCase())) { - return Optional.of(factory.create(context)); - } - } - - return Optional.empty(); + return FormatFactoryUtil.discoverFactory( + FileFormat.class.getClassLoader(), identifier.toLowerCase()) + .create(context); } protected Options getIdentifierPrefixOptions(Options options) { diff --git a/paimon-core/src/main/java/org/apache/paimon/AbstractFileStore.java b/paimon-core/src/main/java/org/apache/paimon/AbstractFileStore.java index 1caff252a654..58cc6f47a9af 100644 --- a/paimon-core/src/main/java/org/apache/paimon/AbstractFileStore.java +++ b/paimon-core/src/main/java/org/apache/paimon/AbstractFileStore.java @@ -105,7 +105,7 @@ protected AbstractFileStore( @Override public FileStorePathFactory pathFactory() { - return pathFactory(options.fileFormat().getFormatIdentifier()); + return pathFactory(options.fileFormatString()); } protected FileStorePathFactory pathFactory(String format) { diff --git a/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java b/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java index 8cf45105c01b..a969fca03777 100644 --- a/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java +++ b/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java @@ -193,7 +193,7 @@ public KeyValueFileStoreWrite newWrite(String commitUser, ManifestCacheFilter ma private Map format2PathFactory() { Map pathFactoryMap = new HashMap<>(); Set formats = new HashSet<>(options.fileFormatPerLevel().values()); - formats.add(options.fileFormat().getFormatIdentifier()); + formats.add(options.fileFormatString()); formats.forEach(format -> pathFactoryMap.put(format, pathFactory(format))); return pathFactoryMap; }