/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2012-2016, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.storage;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.ServiceLoader;
import java.util.Set;
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.internal.util.LazySet;
import org.geotoolkit.lang.Static;
import org.apache.sis.util.ArgumentChecks;
import org.opengis.parameter.ParameterValueGroup;
/**
* Creates {@link DataStore} instances from a set of parameters.
*
* {@section Registration}
* {@link DataStore} factories must implement the {@link DataStoreFactory} interface and declare their
* fully qualified class name in a {@code META-INF/services/org.geotoolkit.storage.DataStoreFactory}
* file. See the {@link ServiceLoader} javadoc for more information.
*
* @author Martin Desruisseaux (Geomatys)
* @author Johann Sorel (Geomatys)
*/
public final class DataStores extends Static {
/**
* The service loader. This loader and its iterator are not synchronized;
* when doing an iteration, the iterator must be used inside synchronized blocks.
*/
private static final ServiceLoader<DataStoreFactory> loader = ServiceLoader.load(DataStoreFactory.class);
/**
* Do not allow instantiation of this class.
*/
private DataStores() {
}
/**
* Returns the set of all factories, optionally filtered by type and availability.
* This method ensures also that the iterator backing the set is properly synchronized.
* <p>
* Note that the iterator doesn't need to be thread-safe; this is the accesses to the
* underlying {@linkplain #loader}, directly or indirectly through its iterator, which
* need to be thread-safe.
*
* @param <T> The type of factories to be returned.
* @param type The type of factories to be returned, or {@code null} for all kind of factories.
* @param all {@code true} for all factories, or {@code false} for only available factories.
* @return The set of factories for the given conditions.
*/
private static synchronized <T extends DataStoreFactory> Set<T> getFactories(final Class<T> type, final boolean all) {
final Set<T> results = new HashSet<>();
final Iterator<DataStoreFactory> factories = loader.iterator();
while (factories.hasNext()) {
final DataStoreFactory candidate = factories.next();
if (type == null || type.isInstance(candidate)) {
if (all || candidate.availability().pass()) {
results.add((T)candidate);
}
}
}
return results;
}
/**
* Returns all factories of the given type, regardless of their
* {@linkplain DataStoreFactory#availability() availability}.
*
* @param <T> The type of the factories to fetch.
* @param type The type of the factories to fetch, or {@code null} for fetching all of them.
* @return The set of all factories of the given type.
*/
public static <T extends DataStoreFactory> Set<T> getAllFactories(final Class<T> type) {
return getFactories(type, true);
}
/**
* Returns factories of the given type which are
* {@linkplain DataStoreFactory#availability() available}.
*
* @param <T> The type of the factories to fetch.
* @param type The type of the factories to fetch, or {@code null} for fetching very types.
* @return The set of available factories of the given type.
*/
public static <T extends DataStoreFactory> Set<T> getAvailableFactories(final Class<T> type) {
return getFactories(type, false);
}
/**
* Returns a factory having an {@linkplain DataStoreFactory#getIdentification() identification}
* equals (ignoring case) to the given string. If more than one factory is found, then this
* method selects an arbitrary one. If no factory is found, then this method returns
* {@code null}.
*
* @param identifier The identifier of the factory to find.
* @return A factory for the given identifier, or {@code null} if none.
*/
public static synchronized DataStoreFactory getFactoryById(final String identifier) {
for (final DataStoreFactory factory : loader) {
if (Citations.identifierMatches(factory.getIdentification().getCitation(), identifier)) {
return factory;
}
}
return null;
}
/**
* Creates a {@link DataStore} instance for the given map of parameter values. This method iterates
* over all {@linkplain #getAvailableFactories(Class) available factories} until a factory
* claiming to {@linkplain DataStoreFactory#canProcess(Map) be able to process} the given
* parameters is found. This factory then {@linkplain DataStoreFactory#open(Map) open}
* the data store.
*
* @param parameters The configuration of the desired data store.
* @return A data store created from the given parameters, or {@code null} if none.
* @throws DataStoreException If a factory is found but can't open the data store.
*/
public static DataStore open(final Map<String, Serializable> parameters) throws DataStoreException {
ArgumentChecks.ensureNonNull("parameters", parameters);
return open(null, parameters);
}
/**
* Creates a {@link DataStore} instance for the given parameters group. This method iterates over
* all {@linkplain #getAvailableFactories(Class) available factories} until a factory claiming
* to {@linkplain DataStoreFactory#canProcess(ParameterValueGroup) be able to process} the given
* parameters is found. This factory then {@linkplain DataStoreFactory#open(ParameterValueGroup)
* open} the data store.
*
* @param parameters The configuration of the desired data store.
* @return A data store created from the given parameters, or {@code null} if none.
* @throws DataStoreException If a factory is found but can't open the data store.
*/
public static DataStore open(final ParameterValueGroup parameters) throws DataStoreException {
ArgumentChecks.ensureNonNull("parameters", parameters);
return open(parameters, null);
}
/**
* Implementation of the public {@code open} method. Exactly one of the {@code parameters}
* and {@code asMap} arguments shall be non-null.
*/
private static synchronized DataStore open(final ParameterValueGroup parameters,
final Map<String, Serializable> asMap) throws DataStoreException
{
CharSequence unavailable = null;
for (final DataStoreFactory factory : loader) {
if ((parameters != null) ? factory.canProcess(parameters) : factory.canProcess(asMap)) {
if (factory.availability().pass()) {
return (DataStore) ((parameters != null) ? factory.open(parameters) : factory.open(asMap));
} else if (unavailable == null) {
unavailable = factory.getDisplayName();
}
}
}
if (unavailable != null) {
throw new DataStoreException("The " + unavailable + " data store is not available. "
+ "Are every required JAR files accessible on the classpath?");
}
return null;
}
/**
* Scans for factory plug-ins on the application class path. This method is needed because the
* application class path can theoretically change, or additional plug-ins may become available.
* Rather than re-scanning the classpath on every invocation of the API, the class path is scanned
* automatically only on the first invocation. Clients can call this method to prompt a re-scan.
* Thus this method need only be invoked by sophisticated applications which dynamically make
* new plug-ins available at runtime.
*/
public static synchronized void scanForPlugins() {
loader.reload();
}
}