blob: d8091d3cd0875cda716a47275f5399458238ae7a [file] [log] [blame]
/*
* 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.sis.storage;
import java.util.logging.Logger;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.metadata.distribution.Format;
import org.apache.sis.internal.simple.SimpleFormat;
import org.apache.sis.internal.storage.URIDataStore;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.metadata.iso.distribution.DefaultFormat;
import org.apache.sis.measure.Range;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Version;
/**
* Provides information about a specific {@link DataStore} implementation.
* There is typically one {@code DataStoreProvider} instance for each format supported by a library.
* Each {@code DataStoreProvider} instances provides the following services:
*
* <ul>
* <li>Provide generic information about the storage (name, <i>etc.</i>).</li>
* <li>Create instances of the {@link DataStore} implementation described by this provider.</li>
* <li>Test if a {@code DataStore} instance created by this provider would have reasonable chances
* to open a given {@link StorageConnector}.</li>
* </ul>
*
* <div class="section">Packaging data stores</div>
* JAR files that provide implementations of this class shall contain an entry with exactly the following path:
*
* {@preformat text
* META-INF/services/org.apache.sis.storage.DataStoreProvider
* }
*
* The above entry shall contain one line for each {@code DataStoreProvider} implementation provided in the JAR file,
* where each line is the fully qualified name of the implementation class.
* See {@link java.util.ServiceLoader} for more general discussion about this lookup mechanism.
*
* <div class="section">Thread safety</div>
* All {@code DataStoreProvider} implementations shall be thread-safe.
* However the {@code DataStore} instances created by the providers do not need to be thread-safe.
*
* @author Martin Desruisseaux (Geomatys)
* @author Johann Sorel (Geomatys)
* @version 1.0
* @since 0.3
* @module
*/
public abstract class DataStoreProvider {
/**
* Name of the parameter that specifies the data store location.
* A parameter named {@value} should be included in the group of parameters returned by {@link #getOpenParameters()}.
* The parameter value is often a {@link java.net.URI} or a {@link java.nio.file.Path}, but other types are allowed.
*
* <p>Implementers are encouraged to define a parameter with this name
* to ensure a common and consistent definition among providers.
* The parameter should be defined as mandatory and typed with a well-known Java class such as
* {@link java.net.URI}, {@link java.nio.file.Path}, JDBC {@linkplain javax.sql.DataSource}, <i>etc</i>.
* The type should have a compact textual representation, for serialization in XML or configuration files.
* Consequently {@link java.io.InputStream} and {@link java.nio.channels.Channel} should be avoided.</p>
*
* @see #CREATE
* @see #getOpenParameters()
*/
public static final String LOCATION = "location";
/**
* Name of the parameter that specifies whether to allow creation of a new {@code DataStore} if none exist
* at the given location. A parameter named {@value} may be included in the group of parameters returned by
* {@link #getOpenParameters()} if the data store supports write operations. The parameter value is often a
* {@link Boolean} and the default value should be {@link Boolean#FALSE} or equivalent.
*
* <p>Implementers are encouraged to define an <em>optional</em> parameter with this name in complement to the
* {@value #LOCATION} parameter <em>only if</em> write operations are supported. If this parameter value is not
* set or is set to {@code false}, then the {@link #open(ParameterValueGroup)} method should fail if no file or
* database exists at the URL or path given by the {@value #LOCATION} parameter. Otherwise if this parameter is
* set to {@code true}, then the {@code open(…)} method may create files, a directory or a database at the given
* location.</p>
*
* <div class="note"><b>Relationship with standard file open options</b>
* <p>For data stores on file systems, a <code>{@value} = true</code> parameter value is equivalent to opening a file
* with {@link java.nio.file.StandardOpenOption#CREATE} and {@link java.nio.file.StandardOpenOption#APPEND APPEND}.
* The other file standard options like {@link java.nio.file.StandardOpenOption#CREATE_NEW CREATE_NEW} and
* {@link java.nio.file.StandardOpenOption#TRUNCATE_EXISTING TRUNCATE_EXISTING} should not be accessible through
* this {@value} parameter. The reason is that {@link ParameterValueGroup} may be used for storing parameters
* permanently (for example in a configuration file or in a database) for reopening the same {@link DataStore}
* many times. File options designed for being used only once like {@code CREATE_NEW} and {@code TRUNCATE_EXISTING}
* are incompatible with this usage.</p></div>
*
* @see #LOCATION
* @see #getOpenParameters()
*/
public static final String CREATE = "create";
/**
* The logger where to reports warnings or change events. Created when first needed and kept
* by strong reference for avoiding configuration lost if the logger if garbage collected.
* This strategy assumes that {@code URIDataStore.Provider} instances are kept alive for
* the duration of JVM lifetime, which is the case with {@link DataStoreRegistry}.
*
* @see #getLogger()
*/
private volatile Logger logger;
/**
* Creates a new provider.
*/
protected DataStoreProvider() {
}
/**
* Returns a short name or abbreviation for the data format.
* This name is used in some warnings or exception messages.
* It may contain any characters, including white spaces
* (i.e. this short name is <strong>not</strong> a format identifier).
*
* <div class="note"><b>Examples:</b>
* {@code "CSV"}, {@code "GeoTIFF"}, {@code "GML"}, {@code "GPX"}, {@code "JPEG"}, {@code "JPEG 2000"},
* {@code "NetCDF"}, {@code "PNG"}, {@code "Shapefile"}.
* </div>
*
* For a more comprehensive format name, see {@link #getFormat()}.
*
* @return a short name or abbreviation for the data format.
*
* @see #getFormat()
*
* @since 0.8
*/
public abstract String getShortName();
/**
* Returns a description of the data format. The description should contain (if available):
*
* <ul>
* <li>A reference to the {@linkplain DefaultFormat#getFormatSpecificationCitation()
* format specification citation}, including:
* <ul>
* <li>a format specification {@linkplain DefaultCitation#getTitle() title}
* (example: <cite>“PNG (Portable Network Graphics) Specification”</cite>),</li>
* <li>the format {@linkplain #getShortName() short name} as a citation
* {@linkplain DefaultCitation#getAlternateTitles() alternate title}
* (example: <cite>“PNG”</cite>),</li>
* <li>the format version as the citation {@linkplain DefaultCitation#getEdition() edition},</li>
* <li>link to an {@linkplain DefaultCitation#getOnlineResources() online} version of the specification.</li>
* </ul>
* </li>
* <li>The title of the {@linkplain DefaultFormat#getFileDecompressionTechnique() file decompression technique}
* used for reading the data.</li>
* </ul>
*
* The default implementation returns a format containing only the value returned by {@link #getShortName()}.
* Subclasses are encouraged to override this method for providing a more complete description, if available.
*
* @return a description of the data format.
*
* @see #getShortName()
* @see DefaultFormat
*
* @since 0.8
*/
public Format getFormat() {
return new SimpleFormat(getShortName());
}
/**
* Returns the range of versions supported by the data store, or {@code null} if unspecified.
*
* @return the range of supported versions, or {@code null} if unspecified.
*
* @since 0.8
*/
public Range<Version> getSupportedVersions() {
return null;
}
/**
* Returns a description of all parameters accepted by this provider for opening a data store.
* Those parameters provide an alternative to {@link StorageConnector} for opening a {@link DataStore}
* from a path or URL, together with additional information like character encoding.
*
* <p>Implementers are responsible for declaring all parameters and whether they are mandatory or optional.
* It is recommended to define at least a parameter named {@value #LOCATION}, completed by {@value #CREATE}
* if the data store supports write operations.
* Those parameters will be recognized by the default {@code DataStoreProvider} methods and used whenever a
* {@link StorageConnector} is required.</p>
*
* <div class="note"><b>Alternative:</b>
* the main differences between the use of {@code StorageConnector} and parameters are:
* <ul class="verbose">
* <li>{@code StorageConnector} is designed for use with file or stream of unknown format;
* the format is automatically detected. By contrast, the use of parameters require to
* determine the format first (i.e. select a {@code DataStoreProvider}).</li>
* <li>Parameters can be used to dynamically generate user configuration interfaces
* and provide fine grain control over the store general behavior such as caching,
* time-outs, encoding, <i>etc</i>.</li>
* <li>Parameters can more easily be serialized in XML or configuration files.</li>
* </ul></div>
*
* @return description of the parameters required or accepted for opening a {@link DataStore}.
*
* @see #LOCATION
* @see #CREATE
* @see #open(ParameterValueGroup)
* @see DataStore#getOpenParameters()
*
* @since 0.8
*/
public abstract ParameterDescriptorGroup getOpenParameters();
/**
* Indicates if the given storage appears to be supported by the {@code DataStore}s created by this provider.
* The most typical return values are:
*
* <ul>
* <li>{@link ProbeResult#SUPPORTED} if the {@code DataStore}s created by this provider
* can open the given storage.</li>
* <li>{@link ProbeResult#UNSUPPORTED_STORAGE} if the given storage does not appear to be in a format
* supported by this {@code DataStoreProvider}.</li>
* </ul>
*
* Note that the {@code SUPPORTED} value does not guarantee that reading or writing will succeed,
* only that there appears to be a reasonable chance of success based on a brief inspection of the
* {@linkplain StorageConnector#getStorage() storage object} or contents.
*
* <p>Implementers are responsible for restoring the input to its original stream position on return of this method.
* Implementers can use a mark/reset pair for this purpose. Marks are available as
* {@link java.nio.ByteBuffer#mark()}, {@link java.io.InputStream#mark(int)} and
* {@link javax.imageio.stream.ImageInputStream#mark()}.</p>
*
* <div class="note"><b>Implementation example</b><br>
* Implementations will typically check the first bytes of the stream for a "magic number" associated
* with the format, as in the following example:
*
* {@preformat java
* public ProbeResult probeContent(StorageConnector storage) throws DataStoreException {
* final ByteBuffer buffer = storage.getStorageAs(ByteBuffer.class);
* if (buffer == null) {
* // If StorageConnector can not provide a ByteBuffer, then the storage is
* // probably not a File, URL, URI, InputStream neither a ReadableChannel.
* return ProbeResult.UNSUPPORTED_STORAGE;
* }
* if (buffer.remaining() < Integer.BYTES) {
* // If the buffer does not contain enough bytes for the integer type, this is not
* // necessarily because the file is truncated. It may be because the data were not
* // yet available at the time this method has been invoked.
* return ProbeResult.INSUFFICIENT_BYTES;
* }
* if (buffer.getInt(buffer.position()) != MAGIC_NUMBER) {
* // We used ByteBuffer.getInt(int) instead than ByteBuffer.getInt() above
* // in order to keep the buffer position unchanged after this method call.
* return ProbeResult.UNSUPPORTED_STORAGE;
* }
* return ProbeResult.SUPPORTED;
* }
* }
* </div>
*
* @param connector information about the storage (URL, stream, JDBC connection, <i>etc</i>).
* @return {@link ProbeResult#SUPPORTED} if the given storage seems to be readable by the {@code DataStore}
* instances created by this provider.
* @throws DataStoreException if an I/O or SQL error occurred. The error shall be unrelated to the logical
* structure of the storage.
*/
public abstract ProbeResult probeContent(StorageConnector connector) throws DataStoreException;
/**
* Returns a data store implementation associated with this provider.
* This method is typically invoked when the format is not known in advance
* (the {@link #probeContent(StorageConnector)} method can be tested on many providers)
* or when the input is not a type accepted by {@link #open(ParameterValueGroup)}
* (for example an {@link java.io.InputStream}).
*
* <div class="section">Implementation note</div>
* Implementers shall invoke {@link StorageConnector#closeAllExcept(Object)} after {@code DataStore}
* creation, keeping open only the needed resource.
*
* @param connector information about the storage (URL, stream, JDBC connection, <i>etc</i>).
* @return a data store implementation associated with this provider for the given storage.
* @throws DataStoreException if an error occurred while creating the data store instance.
*
* @see DataStores#open(Object)
*/
public abstract DataStore open(StorageConnector connector) throws DataStoreException;
/**
* Returns a data store implementation associated with this provider for the given parameters.
* The {@code DataStoreProvider} instance needs to be known before parameters are initialized,
* since the parameters are implementation-dependent. Example:
*
* {@preformat java
* DataStoreProvider provider = ...;
* ParameterValueGroup pg = provider.getOpenParameters().createValue();
* pg.parameter(DataStoreProvider.LOCATION, myURL);
* // Set any other parameters if desired.
* try (DataStore ds = provider.open(pg)) {
* // Use the data store.
* }
* }
*
* <div class="section">Implementation note</div>
* The default implementation gets the value of a parameter named {@value #LOCATION}.
* That value (typically a path or URL) is given to {@link StorageConnector} constructor,
* which is then passed to {@link #open(StorageConnector)}.
*
* @param parameters opening parameters as defined by {@link #getOpenParameters()}.
* @return a data store implementation associated with this provider for the given parameters.
* @throws DataStoreException if an error occurred while creating the data store instance.
*
* @see #LOCATION
* @see #CREATE
* @see #getOpenParameters()
*
* @since 0.8
*/
public DataStore open(final ParameterValueGroup parameters) throws DataStoreException {
ArgumentChecks.ensureNonNull("parameter", parameters);
return open(URIDataStore.Provider.connector(this, parameters));
}
/**
* Returns the logger where to report warnings. This logger is used only if no
* {@link org.apache.sis.storage.event.StoreListener} has been registered for
* {@link org.apache.sis.storage.event.WarningEvent}.
*
* <p>The default implementation returns a logger with the same name as the package name
* of the subclass of this {@code DataStoreProvider} instance. Subclasses should override
* this method if they can provide a more specific logger.</p>
*
* @return the logger to use as a fallback (when there is no listeners) for warning messages.
*
* @since 1.0
*/
public Logger getLogger() {
Logger lg = logger;
if (lg == null) {
logger = lg = Logging.getLogger(getClass());
}
return lg;
}
}