| /* |
| * 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.Locale; |
| import java.util.Map; |
| import java.util.IdentityHashMap; |
| import java.util.Optional; |
| import org.opengis.util.ScopedName; |
| import org.opengis.util.GenericName; |
| import org.opengis.metadata.Metadata; |
| import org.opengis.metadata.Identifier; |
| import org.opengis.metadata.citation.Citation; |
| import org.opengis.metadata.identification.Identification; |
| import org.opengis.parameter.ParameterValueGroup; |
| import org.apache.sis.util.Localized; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.internal.storage.StoreUtilities; |
| import org.apache.sis.internal.storage.Resources; |
| import org.apache.sis.internal.util.Strings; |
| import org.apache.sis.referencing.NamedIdentifier; |
| import org.apache.sis.storage.event.StoreEvent; |
| import org.apache.sis.storage.event.StoreListener; |
| import org.apache.sis.storage.event.StoreListeners; |
| |
| |
| /** |
| * Manages a series of features, coverages or sensor data. |
| * Different {@code DataStore} subclasses exist for different formats (netCDF, GeoTIFF, <i>etc.</i>). |
| * The supported format can be identified by the {@linkplain #getProvider() provider}. |
| * |
| * <p>Each data store is itself a {@link Resource}. The data store subclasses should implement |
| * a more specialized {@code Resource} interface depending on the format characteristics. |
| * For example a {@code DataStore} for ShapeFiles will implement the {@link FeatureSet} interface, |
| * while a {@code DataStore} for netCDF files will implement the {@link Aggregate} interface.</p> |
| * |
| * <div class="section">Thread safety policy</div> |
| * Data stores should be thread-safe, but their synchronization lock is implementation-dependent. |
| * This base class uses only the {@code synchronized} keyword, applied on the following methods: |
| * |
| * <ul> |
| * <li>{@link #getLocale()}</li> |
| * <li>{@link #setLocale(Locale)}</li> |
| * </ul> |
| * |
| * Since above properties are used only for information purpose, concurrent modifications during a read or write |
| * operation should be harmless. Consequently subclasses are free use their own synchronization mechanism instead |
| * than {@code synchronized(this)} lock. |
| * |
| * @author Johann Sorel (Geomatys) |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.0 |
| * |
| * @see DataStores#open(Object) |
| * |
| * @since 0.3 |
| * @module |
| */ |
| public abstract class DataStore implements Resource, Localized, AutoCloseable { |
| /** |
| * The factory that created this {@code DataStore} instance, or {@code null} if unspecified. |
| * This information can be useful for fetching information common to all {@code DataStore} |
| * instances of the same class. |
| * |
| * @since 0.8 |
| * |
| * @see #getProvider() |
| */ |
| protected final DataStoreProvider provider; |
| |
| /** |
| * The store name (typically filename) for formatting error messages, or {@code null} if unknown. |
| * Shall <strong>not</strong> be used as an identifier. |
| * |
| * @see #getDisplayName() |
| */ |
| private final String name; |
| |
| /** |
| * The locale to use for formatting warnings. |
| * This is not the locale for formatting data in the storage. |
| * |
| * @see #getLocale() |
| * @see #setLocale(Locale) |
| */ |
| private Locale locale; |
| |
| /** |
| * The set of registered {@link StoreListener}s for this data store. |
| */ |
| protected final StoreListeners listeners; |
| |
| /** |
| * Creates a new instance with no provider and initially no listener. |
| */ |
| protected DataStore() { |
| provider = null; |
| name = null; |
| locale = Locale.getDefault(Locale.Category.DISPLAY); |
| listeners = new StoreListeners(null, this); |
| } |
| |
| /** |
| * Creates a new instance for the given storage (typically file or database). |
| * The {@code provider} argument is an optional information. |
| * The {@code connector} argument is mandatory. |
| * |
| * @param provider the factory that created this {@code DataStore} instance, or {@code null} if unspecified. |
| * @param connector information about the storage (URL, stream, reader instance, <i>etc</i>). |
| * @throws DataStoreException if an error occurred while creating the data store for the given storage. |
| * |
| * @since 0.8 |
| */ |
| protected DataStore(final DataStoreProvider provider, final StorageConnector connector) throws DataStoreException { |
| ArgumentChecks.ensureNonNull("connector", connector); |
| this.provider = provider; |
| this.name = connector.getStorageName(); |
| this.locale = Locale.getDefault(Locale.Category.DISPLAY); |
| this.listeners = new StoreListeners(null, this); |
| /* |
| * Above locale is NOT OptionKey.LOCALE because we are not talking about the same locale. |
| * The one in this DataStore is for warning and exception messages, not for parsing data. |
| */ |
| } |
| |
| /** |
| * Creates a new instance as a child of another data store instance. |
| * The new instance inherits the parent {@linkplain #getProvider() provider}. |
| * Events created by this {@code DataStore} are forwarded to listeners registered |
| * into the parent data store too. |
| * |
| * @param parent the parent data store, or {@code null} if none. |
| * @param connector information about the storage (URL, stream, reader instance, <i>etc</i>). |
| * @throws DataStoreException if an error occurred while creating the data store for the given storage. |
| * |
| * @since 0.8 |
| */ |
| protected DataStore(final DataStore parent, final StorageConnector connector) throws DataStoreException { |
| ArgumentChecks.ensureNonNull("connector", connector); |
| final StoreListeners forwardTo; |
| if (parent != null) { |
| forwardTo = parent.listeners; |
| provider = parent.provider; |
| locale = parent.locale; |
| } else { |
| forwardTo = null; |
| provider = null; |
| locale = Locale.getDefault(Locale.Category.DISPLAY); |
| } |
| listeners = new StoreListeners(forwardTo, this); |
| name = connector.getStorageName(); |
| } |
| |
| /** |
| * Returns the factory that created this {@code DataStore} instance. |
| * The provider gives additional information on this {@code DataStore} such as a format description |
| * and a list of parameters that can be used for opening data stores of the same class. |
| * |
| * @return the factory that created this {@code DataStore} instance, or {@code null} if unspecified. |
| * |
| * @see #provider |
| * |
| * @since 0.8 |
| */ |
| public DataStoreProvider getProvider() { |
| return provider; |
| } |
| |
| /** |
| * Returns the parameters used to open this data store. |
| * The collection of legal parameters is implementation-dependent |
| * ({@linkplain org.apache.sis.parameter.DefaultParameterValue#getDescriptor() their description} |
| * is given by {@link DataStoreProvider#getOpenParameters()}), |
| * but should contain at least a parameter named {@value org.apache.sis.storage.DataStoreProvider#LOCATION} |
| * with a {@link java.net.URI}, {@link java.nio.file.Path} or {@link javax.sql.DataSource} value. |
| * |
| * <p>In the event a data store must be closed and reopened later, those parameters can be stored in a file or |
| * database and used for {@linkplain DataStoreProvider#open(ParameterValueGroup) creating a new store} later.</p> |
| * |
| * <p>In some cases, for stores reading in-memory data or other inputs that can not fit with |
| * {@code ParameterDescriptorGroup} requirements (for example an {@link java.io.InputStream} |
| * connected to unknown or no {@link java.net.URL}), this method may return null.</p> |
| * |
| * @return parameters used for opening this {@code DataStore}, or {@code null} if not available. |
| * |
| * @see DataStoreProvider#getOpenParameters() |
| * |
| * @since 0.8 |
| */ |
| public abstract ParameterValueGroup getOpenParameters(); |
| |
| /** |
| * Sets the locale to use for formatting warnings and other messages. |
| * In a client-server architecture, it should be the locale on the <em>client</em> side. |
| * |
| * <p>This locale is used on a <cite>best-effort</cite> basis; whether messages will honor this locale or not |
| * depends on the code that logged warnings or threw exceptions. In Apache SIS implementation, this locale has |
| * better chances to be honored by the {@link DataStoreException#getLocalizedMessage()} method rather than |
| * {@code getMessage()}. See {@code getLocalizedMessage()} javadoc for more information.</p> |
| * |
| * @param locale the new locale to use. |
| * |
| * @see DataStoreException#getLocalizedMessage() |
| */ |
| public synchronized void setLocale(final Locale locale) { |
| ArgumentChecks.ensureNonNull("locale", locale); |
| this.locale = locale; |
| } |
| // See class javadoc for a note on synchronization. |
| |
| /** |
| * The locale to use for formatting warnings and other messages. This locale if for user interfaces |
| * only – it has no effect on the data to be read or written from/to the data store. |
| * |
| * <p>The default value is the {@linkplain Locale#getDefault() system default locale}.</p> |
| * |
| * @see org.apache.sis.storage.event.StoreEvent#getLocale() |
| */ |
| @Override |
| public synchronized Locale getLocale() { |
| return locale; |
| } |
| |
| /** |
| * Returns a short name or label for this data store. |
| * The returned name can be used in user interfaces or in error messages. |
| * It may be a title in natural language, but should be relatively short. |
| * The name may be localized in the language specified by the value of {@link #getLocale()} |
| * if this data store is capable to produce a name in various languages. |
| * |
| * <p>This name should not be used as an identifier since there is no guarantee that the name |
| * is unique among data stores, and no guarantee that the name is the same in all locales. |
| * The name may also contain any Unicode characters, including characters usually not allowed |
| * in identifiers like white spaces.</p> |
| * |
| * <p>This method should never throw an exception since it may be invoked for producing error |
| * messages, in which case throwing an exception here would mask the original exception.</p> |
| * |
| * <p>This method differs from {@link #getIdentifier()} in that it is typically a file name |
| * known at construction time instead than a property read from metadata. |
| * Default implementation returns the {@link StorageConnector#getStorageName()} value, |
| * or {@code null} if this data store has been created by the no-argument constructor. |
| * Subclasses should override this method if they can provide a better name.</p> |
| * |
| * @return a short name of label for this data store, or {@code null} if unknown. |
| * |
| * @see #getIdentifier() |
| * @see #getLocale() |
| * |
| * @since 0.8 |
| */ |
| public String getDisplayName() { |
| return name; |
| } |
| |
| /** |
| * Returns an identifier for the root resource of this data store, or {@code null} if none. |
| * If this data store contains many resources (as in an {@link Aggregate}), |
| * the returned identifier shall be different than the identifiers of those child resources. |
| * In other words, the following equality shall hold without ambiguity: |
| * |
| * {@preformat java |
| * findResource(getIdentifier().toString()) == this |
| * } |
| * |
| * Note that this identifier is not guaranteed to be unique between different {@code DataStore} instances; |
| * it only needs to be unique among the resources provided by this data store instance. |
| * |
| * <div class="section">Default implementation</div> |
| * <p>The default implementation searches for an identifier in the metadata, |
| * at the location shown below, provided that conditions are met:</p> |
| * |
| * <blockquote> |
| * <p><b>Path:</b> {@link Resource#getMetadata() metadata} / |
| * {@link org.apache.sis.metadata.iso.DefaultMetadata#getIdentificationInfo() identificationInfo} / |
| * {@link org.apache.sis.metadata.iso.identification.AbstractIdentification#getCitation() citation} / |
| * {@link org.apache.sis.metadata.iso.citation.DefaultCitation#getIdentifiers() identifier}</p> |
| * |
| * <p><b>Condition:</b> in default implementation, the identifier is presents only if exactly one |
| * {@code citation} is found at above path. If two or more {@code citation} instances are found, |
| * the identification is considered ambiguous and an empty value is returned.</p> |
| * |
| * <p><b>Selection:</b> the first identifier implementing the {@code GenericName} interface is returned. |
| * If there is no such identifier, then a {@link org.apache.sis.referencing.NamedIdentifier} is created |
| * from the first identifier. If there is no identifier at all, then an empty value is returned.</p> |
| * </blockquote> |
| * |
| * Subclasses are encouraged to override this method with more efficient implementations. |
| * |
| * @return an identifier for the root resource of this data store. |
| * @throws DataStoreException if an error occurred while fetching the identifier. |
| * |
| * @see #getMetadata() |
| * @see #getDisplayName() |
| * |
| * @since 1.0 |
| */ |
| @Override |
| public Optional<GenericName> getIdentifier() throws DataStoreException { |
| final Metadata metadata = getMetadata(); |
| if (metadata != null) { |
| Citation citation = null; |
| for (final Identification id : metadata.getIdentificationInfo()) { |
| final Citation c = id.getCitation(); |
| if (c != null) { |
| if (citation != null && citation != c) { |
| return Optional.empty(); // Ambiguity. |
| } |
| citation = c; |
| } |
| } |
| if (citation != null) { |
| Identifier first = null; |
| for (final Identifier c : citation.getIdentifiers()) { |
| if (c instanceof GenericName) { |
| return Optional.of((GenericName) c); |
| } else if (first == null) { |
| first = c; |
| } |
| } |
| if (first != null) { |
| return Optional.of(new NamedIdentifier(first)); |
| } |
| } |
| } |
| return Optional.empty(); |
| } |
| |
| /** |
| * Returns information about the data store as a whole. The returned metadata object can contain |
| * information such as the spatiotemporal extent of all contained {@linkplain Resource resources}, |
| * contact information about the creator or distributor, data quality, update frequency, usage constraints, |
| * file format and more. |
| * |
| * @return information about resources in the data store, or {@code null} if none. |
| * @throws DataStoreException if an error occurred while reading the data. |
| * |
| * @see #getIdentifier() |
| */ |
| @Override |
| public abstract Metadata getMetadata() throws DataStoreException; |
| |
| /** |
| * Searches for a resource identified by the given identifier. The given identifier should be the string |
| * representation of the return value of {@link Resource#getIdentifier()} on the desired resource. |
| * Implementation may also accept aliases for convenience. For example if the full name of a resource |
| * is {@code "foo:bar"}, then this method may accept {@code "bar"} as a synonymous of {@code "foo:bar"} |
| * provided that it does not introduce ambiguity. |
| * |
| * <p>The default implementation verifies if above criterion matches to this {@code DataStore} |
| * (which is itself a resource), then iterates recursively over {@link Aggregate} components |
| * if this data store is an aggregate. |
| * If a match is found without ambiguity, the associated resource is returned. Otherwise an exception is thrown. |
| * Subclasses are encouraged to override this method with a more efficient implementation.</p> |
| * |
| * @param identifier identifier of the resource to fetch. Must be non-null. |
| * @return resource associated to the given identifier (never {@code null}). |
| * @throws IllegalNameException if no resource is found for the given identifier, or if more than one resource is found. |
| * @throws DataStoreException if another kind of error occurred while searching resources. |
| * |
| * @see Resource#getIdentifier() |
| * @see FeatureNaming |
| */ |
| public Resource findResource(final String identifier) throws DataStoreException { |
| ArgumentChecks.ensureNonEmpty("identifier", identifier); |
| final Resource resource = findResource(identifier, this, new IdentityHashMap<>()); |
| if (resource != null) { |
| return resource; |
| } |
| throw new IllegalNameException(StoreUtilities.resourceNotFound(this, identifier)); |
| } |
| |
| /** |
| * Recursively searches for a resource identified by the given identifier. |
| * This is the implementation of {@link #findResource(String)}. |
| * |
| * @param identifier identifier of the resource to fetch. |
| * @param candidate a resource to compare against the identifier. |
| * @param visited resources visited so-far, for avoiding never-ending loops if cycles exist. |
| * @return resource associated to the given identifier, or {@code null} if not found. |
| */ |
| private Resource findResource(final String identifier, final Resource candidate, |
| final Map<Resource,Boolean> visited) throws DataStoreException |
| { |
| if (candidate != null && visited.put(candidate, Boolean.TRUE) == null) { |
| GenericName name = candidate.getIdentifier().orElse(null); |
| if (name != null) { |
| do if (identifier.equals(name.toString())) { |
| return candidate; |
| } |
| while ((name instanceof ScopedName) && name != (name = ((ScopedName) name).tail())); |
| } |
| if (candidate instanceof Aggregate) { |
| Resource result = null; |
| for (final Resource child : ((Aggregate) candidate).components()) { |
| final Resource match = findResource(identifier, child, visited); |
| if (match != null) { |
| if (result == null) { |
| result = match; |
| } else { |
| throw new IllegalNameException(Resources.forLocale(getLocale()) |
| .getString(Resources.Keys.ResourceIdentifierCollision_2, getDisplayName(), identifier)); |
| } |
| } |
| } |
| return result; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Registers a listener to notify when the specified kind of event occurs in this data store or in a resource. |
| * The data store will call the {@link StoreListener#eventOccured(StoreEvent)} method when new events matching |
| * the {@code eventType} occur. An event may be a change in data store content or structure, or a warning that |
| * occurred during a read or write operation. |
| * |
| * <p>Registering a listener for a given {@code eventType} also register the listener for all event sub-types. |
| * The same listener can be registered many times, but its {@link StoreListener#eventOccured(StoreEvent)} |
| * method will be invoked only once per event. This filtering applies even if the listener is registered |
| * on individual resources of this data store.</p> |
| * |
| * <p>If this data store may produce events of the given type, then the given listener is kept by strong reference; |
| * it will not be garbage collected unless {@linkplain #removeListener(Class, StoreListener) explicitly removed} |
| * or unless this {@code DataStore} is itself garbage collected. However if the given type of events can never |
| * happen with this data store, then this method is not required to keep a reference to the given listener.</p> |
| * |
| * <div class="section">Warning events</div> |
| * If {@code eventType} is assignable from <code>{@linkplain org.apache.sis.storage.event.WarningEvent}.class</code>, |
| * then registering that listener turns off logging of warning messages for this data store. |
| * This side-effect is applied on the assumption that the registered listener will handle |
| * warnings in its own way, for example by showing warnings in a widget. |
| * |
| * @param <T> compile-time value of the {@code eventType} argument. |
| * @param eventType type of {@link StoreEvent} to listen (can not be {@code null}). |
| * @param listener listener to notify about events. |
| * |
| * @since 1.0 |
| */ |
| @Override |
| public <T extends StoreEvent> void addListener(Class<T> eventType, StoreListener<? super T> listener) { |
| listeners.addListener(eventType, listener); |
| } |
| |
| /** |
| * Unregisters a listener previously added to this data store for the given type of events. |
| * The {@code eventType} must be the exact same class than the one given to the {@code addListener(…)} method; |
| * this method does not remove listeners registered for subclasses and does not remove listeners registered in |
| * children resources. |
| * |
| * <p>If the same listener has been registered many times for the same even type, then this method removes only |
| * the most recent registration. In other words if {@code addListener(type, ls)} has been invoked twice, then |
| * {@code removeListener(type, ls)} needs to be invoked twice in order to remove all instances of that listener. |
| * If the given listener is not found, then this method does nothing (no exception is thrown).</p> |
| * |
| * <div class="section">Warning events</div> |
| * If {@code eventType} is <code>{@linkplain org.apache.sis.storage.event.WarningEvent}.class</code> |
| * and if, after this method invocation, there is no remaining listener for warning events, |
| * then this {@code DataStore} will send future warnings to the loggers. |
| * |
| * @param <T> compile-time value of the {@code eventType} argument. |
| * @param eventType type of {@link StoreEvent} which were listened (can not be {@code null}). |
| * @param listener listener to stop notifying about events. |
| * |
| * @since 1.0 |
| */ |
| @Override |
| public <T extends StoreEvent> void removeListener(Class<T> eventType, StoreListener<? super T> listener) { |
| listeners.removeListener(eventType, listener); |
| } |
| |
| /** |
| * Closes this data store and releases any underlying resources. |
| * |
| * @throws DataStoreException if an error occurred while closing this data store. |
| */ |
| @Override |
| public abstract void close() throws DataStoreException; |
| |
| /** |
| * Returns a string representation of this data store for debugging purpose. |
| * The content of the string returned by this method may change in any future SIS version. |
| * |
| * @return a string representation of this data store for debugging purpose. |
| */ |
| @Override |
| public String toString() { |
| return Strings.bracket(getClass(), getDisplayName()); |
| } |
| } |