in endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStoreRegistry.java [207:392]
private ProbeProviderPair lookup(final Object storage, final Capability capability,
Predicate<DataStoreProvider> preferred, final boolean open)
throws DataStoreException
{
final boolean writable;
StorageConnector connector; // Will be reset to `null` if it shall not be closed.
if (storage instanceof StorageConnector) {
connector = (StorageConnector) storage;
writable = (capability == Capability.WRITE) && connector.getOption(OptionKey.OPEN_OPTIONS) == null;
final var filter = connector.getOption(InternalOptionKey.PREFERRED_PROVIDERS);
if (filter != null) {
preferred = (preferred != null) ? preferred.and(filter) : filter;
}
} else {
connector = new StorageConnector(storage);
writable = (capability == Capability.WRITE);
}
/*
* If this method is invoked by `DataStores.openWritable(…)`, add NIO open options if not already present.
* Note that this code may modify a user provided storage connector. It should be okay considering that
* each `StorageConnector` instance should be short-lived and used only once.
*/
if (writable) {
connector.setOption(OptionKey.OPEN_OPTIONS, new StandardOpenOption[] {
StandardOpenOption.CREATE, StandardOpenOption.WRITE
});
}
if (preferred != null) {
connector.setOption(InternalOptionKey.PREFERRED_PROVIDERS, preferred);
}
/*
* If we can get a filename extension from the given storage (file, URL, etc.), we may perform two times
* more iterations on the provider list. One serie of iterations will use only the providers that declare
* capability to read or write files of that suffix (Category.SUFFIX_MATCH). Only if no provider has been
* able to read or write that file, we will do another iteration on other providers (Category.IGNORE_SUFFIX).
* The intent is to avoid DataStoreProvider.probeContent(…) invocations loading large dependencies.
*/
final String extension = connector.getFileExtension();
final boolean useSuffix = !Strings.isNullOrEmpty(extension);
final boolean isWriteOnly = (capability == Capability.WRITE) && IOUtilities.isWriteOnly(connector.getStorage());
ProbeProviderPair selected = null;
final var needMoreBytes = new LinkedList<ProbeProviderPair>();
try {
boolean isFirstIteration = true;
search: for (final Category category : Category.values()) {
if (category.preferred && (preferred == null)) continue;
if (category.useSuffix && !useSuffix) continue;
/*
* All usages of `loader` and its `providers` iterator must be protected in a synchronized block,
* because ServiceLoader is not thread-safe. We try to keep the synhronization block as small as
* possible for less contention. In particular, the `probeContent(connector)` method call may be
* costly.
*/
final Iterator<DataStoreProvider> providers;
DataStoreProvider provider;
synchronized (loader) {
providers = loader.iterator();
provider = providers.hasNext() ? providers.next() : null;
}
while (provider != null) {
/*
* Check if the provider should be tested in current iteration.
* The criteria for testing a provider is determined by comparing
* provider metadata with the category tested in current iteration.
*/
boolean accept;
final StoreMetadata md = provider.getClass().getAnnotation(StoreMetadata.class);
if (md == null) {
accept = isFirstIteration; // If no metadata, test only during one iteration.
} else {
accept = (category.preferred || md.yieldPriority() == category.yieldPriority) &&
ArraysExt.contains(md.capabilities(), capability);
if (accept & useSuffix) {
accept = ArraysExt.containsIgnoreCase(md.fileSuffixes(), extension) == category.useSuffix;
}
}
if (accept & (preferred != null)) {
accept = (preferred.test(provider) == category.preferred);
}
/*
* At this point, it has been determined whether the provider should be tested in current iteration.
* If accepted, perform now the probing operation for checking if the current provider is suitable.
* The `connector.probing` field is set to a non-null value for telling `StorageConnector` to not
* create empty file if the file does not exist (it has no effect in read-only mode).
*/
if (accept) {
final var candidate = new ProbeProviderPair(provider);
if (isWriteOnly) {
/*
* We cannot probe a write-only storage. Rely on the filtering done before this block,
* which was based on format name and file suffix, and use the first filtered provider.
*/
selected = candidate;
break search;
}
final ProbeProviderPair old = connector.probing;
final ProbeResult probe;
try {
connector.probing = candidate;
probe = provider.probeContent(connector);
} finally {
connector.probing = old;
}
candidate.probe = probe;
if (probe.isSupported()) {
/*
* Stop at the first provider claiming to be able to read the storage.
* Do not iterate over the list of deferred providers (if any).
*/
selected = candidate;
break search;
}
if (ProbeResult.INSUFFICIENT_BYTES.equals(probe)) {
/*
* If a provider doesn't have enough bytes for answering the question,
* try again after this loop with more bytes in the buffer, unless we
* found another provider.
*/
needMoreBytes.add(candidate);
} else if (ProbeResult.UNDETERMINED.equals(probe)) {
/*
* If a provider doesn't know whether it can open the given storage,
* we will try it only if we find no provider retuning SUPPORTED.
* We select the first provider because it is more likely to be the
* one for the file extension of the given storage.
*/
if (selected == null) {
selected = candidate;
}
}
}
synchronized (loader) {
provider = providers.hasNext() ? providers.next() : null;
}
}
/*
* If any provider did not had enough bytes for answering the `probeContent(…)` question,
* get more bytes and try again. We try to prefetch more bytes only if we have no choice
* in order to avoid latency on network connection.
*/
while (!needMoreBytes.isEmpty() && connector.prefetch()) {
for (final Iterator<ProbeProviderPair> it = needMoreBytes.iterator(); it.hasNext();) {
final ProbeProviderPair p = it.next();
p.probe = p.provider.probeContent(connector);
if (p.probe.isSupported()) {
selected = p;
break search;
}
if (!ProbeResult.INSUFFICIENT_BYTES.equals(p.probe)) {
if (selected == null && ProbeResult.UNDETERMINED.equals(p.probe)) {
selected = p; // To be used only if we don't find a better match.
}
it.remove(); // UNSUPPORTED_* or UNDETERMINED: do not try again those providers.
}
}
}
/*
* If we filtered providers by the file extension without finding a suitable provider,
* try again with all other providers (even if they are for another file extension).
* We do that by moving to the next `Category`.
*/
isFirstIteration = false;
}
/*
* If a provider has been found, or if a provider returned UNDETERMINED, use that one
* for opening a DataStore. Note that if more than one provider returned UNDETERMINED,
* the selected one is arbitrary and may change in different execution. Implementers
* shall avoid the UNDETERMINED value as much as possible (this value should be used
* only for RAW image format).
*/
if (open && selected != null) {
selected.store = selected.provider.open(connector);
connector = null; // For preventing it to be closed.
}
} finally {
if (connector != null && connector != storage) {
connector.closeAllExcept(null);
}
}
if (open && selected == null) {
@SuppressWarnings("null") // `connector` is null only if `selected` is non-null.
final String name = connector.getStorageName();
throw new UnsupportedStorageException(null, Resources.Keys.UnknownFormatFor_1, name);
}
return selected;
}