/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/
package alluxio.underfs;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ServiceLoader;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.concurrent.NotThreadSafe;
/**
* <p>
* Central registry of available {@link UnderFileSystemFactory} instances that uses the
* {@link ServiceLoader} mechanism to automatically discover available factories and provides a
* central place for obtaining actual {@link UnderFileSystem} instances.
* </p>
* <h3>Registering New Factories</h3>
* <p>
* New factories can be registered either using the {@linkplain ServiceLoader} based automatic
* discovery mechanism or manually using the static {@link #register(UnderFileSystemFactory)}
* method. The down-side of the automatic discovery mechanism is that the discovery order is not
* controllable. As a result if your implementation is designed as a replacement for one of the
* standard implementations, depending on the order in which the JVM discovers the services your own
* implementation may not take priority. You can enable {@code DEBUG} level logging for this class
* to see the order in which factories are discovered and which is selected when obtaining a
* {@link UnderFileSystem} instance for a path. If this shows that your implementation is not
* getting discovered or used, you may wish to use the manual registration approach.
* </p>
* <h4>Automatic Discovery</h4>
* <p>
* To use the {@linkplain ServiceLoader} based mechanism you need to have a file named
* {@code alluxio.underfs.UnderFileSystemFactory} placed in the {@code META-INF\services} directory
* of your project. This file should contain the full name of your factory types (one per line),
* your factory types must have a public unparameterised constructor available (see
* {@link ServiceLoader} for more detail on this). You can enable {@code DEBUG} level logging to see
* factories as they are discovered if you wish to check that your implementation gets discovered.
* </p>
* <p>
* Note that if you are bundling Alluxio plus your code in a shaded JAR using Maven, make sure to
* use the {@code ServicesResourceTransformer} as otherwise your services file will override the
* core provided services file and leave the standard factories and under file system
* implementations unavailable.
* </p>
* <h4>Manual Registration</h4>
* <p>
* To manually register a factory, simply pass an instance of your factory to the
* {@link #register(UnderFileSystemFactory)} method. This can be useful when your factory cannot be
* instantiated without arguments or in cases where automatic discovery does not give your factory
* priority. Factories registered this way will be registered at the start of the factories list so
* will have the first opportunity to indicate whether they support a requested path.
* </p>
*/
@NotThreadSafe
public final class UnderFileSystemFactoryRegistry {
private static final Logger LOG = LoggerFactory.getLogger(UnderFileSystemFactoryRegistry.class);
private static final List<UnderFileSystemFactory> FACTORIES = new CopyOnWriteArrayList<>();
private static boolean sInit = false;
// prevent instantiation
private UnderFileSystemFactoryRegistry() {}
static {
// Call the actual initializer which is a synchronized method for thread safety purposes
init();
}
/**
* Returns a read-only view of the available factories.
*
* @return Read-only view of the available factories
*/
public static List<UnderFileSystemFactory> available() {
return Collections.unmodifiableList(FACTORIES);
}
/**
* Finds the first Under File System factory that supports the given path.
*
* @param path path
* @return factory if available, null otherwise
*/
public static UnderFileSystemFactory find(String path) {
Preconditions.checkArgument(path != null, "path may not be null");
for (UnderFileSystemFactory factory : FACTORIES) {
if (factory.supportsPath(path)) {
LOG.debug("Selected Under File System Factory implementation {} for path {}",
factory.getClass(), path);
return factory;
}
}
LOG.warn("No Under File System Factory implementation supports the path {}. Please check if "
+ "the under storage path is valid.", path);
return null;
}
/**
* Finds all the Under File System factories that support the given path.
*
* @param path path
* @return list of factories that support the given path which may be an empty list
*/
public static List<UnderFileSystemFactory> findAll(String path) {
Preconditions.checkArgument(path != null, "path may not be null");
List<UnderFileSystemFactory> eligibleFactories = new ArrayList<>();
for (UnderFileSystemFactory factory : FACTORIES) {
if (factory.supportsPath(path)) {
LOG.debug("Under File System Factory implementation {} is eligible for path {}",
factory.getClass(), path);
eligibleFactories.add(factory);
}
}
if (eligibleFactories.isEmpty()) {
LOG.warn("No Under File System Factory implementation supports the path {}", path);
}
return eligibleFactories;
}
private static synchronized void init() {
if (sInit) {
return;
}
// Discover and register the available factories
ServiceLoader<UnderFileSystemFactory> discoveredFactories =
ServiceLoader.load(UnderFileSystemFactory.class,
UnderFileSystemFactory.class.getClassLoader());
for (UnderFileSystemFactory factory : discoveredFactories) {
LOG.debug("Discovered Under File System Factory implementation {} - {}", factory.getClass(),
factory.toString());
FACTORIES.add(factory);
}
sInit = true;
}
/**
* Registers a new factory.
* <p>
* Factories are registered at the start of the factories list so they can override the existing
* automatically discovered factories. Generally if you use the {@link ServiceLoader} mechanism
* properly it should be unnecessary to call this, however since ServiceLoader discovery order
* may be susceptible to class loader behavioral differences there may be rare cases when you
* need to manually register the desired factory.
* </p>
*
* @param factory factory to register
*/
public static void register(UnderFileSystemFactory factory) {
if (factory == null) {
return;
}
LOG.debug("Registered Under File System Factory implementation {} - {}", factory.getClass(),
factory.toString());
// Insert at start of list so it will take precedence over automatically discovered and
// previously registered factories
FACTORIES.add(0, factory);
}
/**
* Resets the registry to its default state
* <p>
* This clears the registry as it stands and rediscovers the available factories.
* </p>
*/
public static synchronized void reset() {
if (sInit) {
// Reset state
sInit = false;
FACTORIES.clear();
}
// Reinitialise
init();
}
/**
* Unregisters an existing factory.
*
* @param factory factory to unregister
*/
public static void unregister(UnderFileSystemFactory factory) {
if (factory == null) {
return;
}
LOG.debug("Unregistered Under File System Factory implementation {} - {}", factory.getClass(),
factory.toString());
FACTORIES.remove(factory);
}
}