Author: desruisseaux Date: Thu Feb 22 22:18:05 2018 New Revision: 1825102 URL: http://svn.apache.org/viewvc?rev=1825102&view=rev Log: Move the Store.Writable internal class as a separated class: WritableStore. Be more conservative before to delete files, e.g. delete only if direct children of the directory managed by the store. Avoid calls to components().contains(resources); check file existence instead. Take StandardOpenOption in account when creating a folder store. Replace strings by localized resources in exception messages. Move some code in a StoreUtilities class so it can be shared. Move some checks to the constructor.
Added: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java (with props) sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/WritableStore.java - copied, changed from r1824938, sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Citations.java sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FileSystemResource.java sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Citations.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Citations.java?rev=1825102&r1=1825101&r2=1825102&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Citations.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Citations.java [UTF-8] Thu Feb 22 22:18:05 2018 @@ -473,7 +473,16 @@ public final class Citations extends Sta * after we moved the {@code sis-utility} code that use this method. */ public static String getUnicodeIdentifier(final Citation citation) { - final String identifier = getIdentifier(citation, true); + return removeIgnorableCharacters(getIdentifier(citation, true)); + } + + /** + * Removes characters that are ignorable according Unicode specification. + * + * @param identifier the character sequence from which to remove ignorable characters, or {@code null}. + * @return a character sequence with ignorable character removed. May be the same instance than the given argument. + */ + public static String removeIgnorableCharacters(final String identifier) { if (identifier != null) { /* * First perform a quick check to see if there is any ignorable characters. Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FileSystemResource.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FileSystemResource.java?rev=1825102&r1=1825101&r2=1825102&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FileSystemResource.java [UTF-8] (original) +++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FileSystemResource.java [UTF-8] Thu Feb 22 22:18:05 2018 @@ -24,8 +24,17 @@ import org.apache.sis.storage.Resource; /** * A resource which is loaded from one or many files on an arbitrary file system. This interface * allows a resource (typically a {@linkplain org.apache.sis.storage.DataStore data store}) to - * list the files that it uses. This information can be used for improving data management, - * for example copy operations. + * list the files that it uses. This is for informative purpose only and should not be used for + * copying or deleting resources. + * + * <div class="section">Alternatives</div> + * <p>For copying data from one location to another, consider using + * {@link org.apache.sis.storage.WritableAggregate#add(Resource)} instead. + * The data store implementations may detect that some {@code add(…)} operations + * can be performed by verbatim copy of files.</p> + * + * <p>For deleting data, consider using + * {@link org.apache.sis.storage.WritableAggregate#remove(Resource)} instead.</p> * * @author Johann Sorel (Geomatys) * @version 1.0 @@ -37,11 +46,31 @@ import org.apache.sis.storage.Resource; */ public interface FileSystemResource extends Resource { /** - * Gets the paths to all files used by this resource. This is typically the - * files opened by a {@linkplain org.apache.sis.storage.DataStore data store}. + * Gets the paths to files potentially used by this resource. + * This is typically the files opened by a {@linkplain org.apache.sis.storage.DataStore data store}. + * There is no guarantee that all files are in the same directory or that each file is used exclusively + * by this data source (e.g. no guarantee that modifying or deleting a file will not impact other resources). + * + * <div class="note"><b>Example:</b> + * a resources created for a GRIB file may use the following component files: + * <ul> + * <li>The main GRIB file.</li> + * <li>If managed by the UCAR library, two auxiliary files next to the main GRIB file: + * the index file ({@code ".gbx9"}) and the collection file ({@code ".ncx"}). + * Those two files are owned exclusively by the resource.</li> + * <li>Eventually a GRIB table file. This table may be located in a path unrelated to + * to the path of the main file and may be shared by many resources.</li> + * </ul> + * </div> + * + * This method should return paths to files only. It should not return paths to directories. * * @return files used by this resource. Should never be {@code null}. * @throws DataStoreException if an error on the file system prevent the creation of the list. + * + * @todo Rename {@code getComponentFiles()}? The reason is that it is not returning the paths of + * multiple resources, but paths to components of this single resources. The use of "Files" + * is for emphasing that this method should not return path to directories. */ Path[] getResourcePaths() throws DataStoreException; } Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java?rev=1825102&r1=1825101&r2=1825102&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java [UTF-8] (original) +++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java [UTF-8] Thu Feb 22 22:18:05 2018 @@ -67,7 +67,12 @@ public final class Resources extends Ind public static final short AmbiguousName_4 = 15; /** - * Can not get metadata common to “{0}” files. Reason: {1} + * Can not create resources based on the content of “{0}” directory. + */ + public static final short CanNotCreateFolderStore_1 = 43; + + /** + * Can not get metadata common to “{0}” files. The reason is: {1} */ public static final short CanNotGetCommonMetadata_2 = 39; @@ -97,6 +102,16 @@ public final class Resources extends Ind public static final short CanNotReadFile_4 = 3; /** + * Can not remove resource “{1}” from aggregate “{0}”. + */ + public static final short CanNotRemoveResource_2 = 49; + + /** + * Can not save resources of type ‘{1}’ in a “{0}” store. + */ + public static final short CanNotStoreResourceType_2 = 41; + + /** * This {0} reader is closed. */ public static final short ClosedReader_1 = 4; @@ -137,6 +152,11 @@ public final class Resources extends Ind public static final short DataStoreTimeZone = 32; /** + * Name of the format to use for reading or writing the directory content. + */ + public static final short DirectoryContentFormatName = 40; + + /** * Content of “{0}” directory. */ public static final short DirectoryContent_1 = 35; @@ -158,9 +178,14 @@ public final class Resources extends Ind public static final short FeatureNotFound_2 = 17; /** - * Name of the format to use for reading or writing the directory content. + * A {1,choice,0#file|1#directory} already exists at “{0}”. + */ + public static final short FileAlreadyExists_2 = 45; + + /** + * The “{0}” file is not a directory of resources. */ - public static final short FolderStoreProviderParameter = 40; + public static final short FileIsNotAResourceDirectory_1 = 44; /** * Whether to assemble trajectory fragments (lines in CSV file) in a single feature instance. @@ -189,16 +214,41 @@ public final class Resources extends Ind public static final short InconsistentNameComponents_2 = 10; /** + * Resource “{0}” does not have an identifier. + */ + public static final short MissingResourceIdentifier_1 = 42; + + /** * Missing scheme in “{0}” URI. */ public static final short MissingSchemeInURI_1 = 11; /** + * No directory of resources found at “{0}”. + */ + public static final short NoSuchResourceDirectory_1 = 46; + + /** + * Resource “{1}” is not part of aggregate “{0}”. + */ + public static final short NoSuchResourceInAggregate_2 = 50; + + /** + * Resource “{0}” is not a writable feature set. + */ + public static final short NotAWritableFeatureSet_1 = 47; + + /** * Processing executed on {0}. */ public static final short ProcessingExecutedOn_1 = 12; /** + * A resource already exists at “{0}”. + */ + public static final short ResourceAlreadyExists_1 = 48; + + /** * More than one resource have the “{1}” identifier in the “{0}” data store. */ public static final short ResourceIdentifierCollision_2 = 23; Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties?rev=1825102&r1=1825101&r2=1825102&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties [ISO-8859-1] (original) +++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties [ISO-8859-1] Thu Feb 22 22:18:05 2018 @@ -20,12 +20,15 @@ # For resources shared by all modules in the Apache SIS project, see "org.apache.sis.util.resources" package. # AmbiguousName_4 = Name \u201c{3}\u201d is ambiguous because it can be understood as either \u201c{1}\u201d or \u201c{2}\u201d in the context of \u201c{0}\u201d data. -CanNotGetCommonMetadata_2 = Can not get metadata common to \u201c{0}\u201d files. Reason: {1} +CanNotCreateFolderStore_1 = Can not create resources based on the content of \u201c{0}\u201d directory. +CanNotGetCommonMetadata_2 = Can not get metadata common to \u201c{0}\u201d files. The reason is: {1} CanNotReadCRS_WKT_1 = Can not read the Coordinate Reference System (CRS) Well Known Text (WKT) in \u201c{0}\u201d. CanNotReadDirectory_1 = Can not read \u201c{0}\u201d directory. CanNotReadFile_2 = Can not read \u201c{1}\u201d as a file in the {0} format. CanNotReadFile_3 = Can not read line {2} of \u201c{1}\u201d as part of a file in the {0} format. CanNotReadFile_4 = Can not read line {2} (after column {3}) of \u201c{1}\u201d as part of a file in the {0} format. +CanNotRemoveResource_2 = Can not remove resource \u201c{1}\u201d from aggregate \u201c{0}\u201d. +CanNotStoreResourceType_2 = Can not save resources of type \u2018{1}\u2019 in a \u201c{0}\u201d store. ClosedReader_1 = This {0} reader is closed. ClosedWriter_1 = This {0} writer is closed. ConcurrentRead_1 = One or more read operations are in progress in the \u201c{0}\u201d data store. @@ -35,17 +38,24 @@ DataStoreLocale = Form DataStoreLocation = Data store location as a file or URL. DataStoreTimeZone = Timezone of dates in the data store. DirectoryContent_1 = Content of \u201c{0}\u201d directory. +DirectoryContentFormatName = Name of the format to use for reading or writing the directory content. FeatureAlreadyPresent_2 = A feature named \u201c{1}\u201d is already present in the \u201c{0}\u201d data store. FeatureNotFound_2 = Feature \u201c{1}\u201d has not been found in the \u201c{0}\u201d data store. -FolderStoreProviderParameter = Name of the format to use for reading or writing the directory content. +FileAlreadyExists_2 = A {1,choice,0#file|1#directory} already exists at \u201c{0}\u201d. +FileIsNotAResourceDirectory_1 = The \u201c{0}\u201d file is not a directory of resources. FoliationRepresentation = Whether to assemble trajectory fragments (lines in CSV file) in a single feature instance. ExcessiveStringSize_3 = Character string in the \u201c{0}\u201d file is too long. The string has {2} characters while the limit is {1}. IllegalFeatureType_2 = The {0} data store does not accept features of type \u201c{1}\u201d. IllegalInputTypeForReader_2 = The {0} reader does not accept inputs of type \u2018{1}\u2019. IllegalOutputTypeForWriter_2 = The {0} writer does not accept outputs of type \u2018{1}\u2019. InconsistentNameComponents_2 = Components of the \u201c{1}\u201d name are inconsistent with those of the name previously binded in \u201c{0}\u201d data store. +MissingResourceIdentifier_1 = Resource \u201c{0}\u201d does not have an identifier. MissingSchemeInURI_1 = Missing scheme in \u201c{0}\u201d URI. +NoSuchResourceDirectory_1 = No directory of resources found at \u201c{0}\u201d. +NoSuchResourceInAggregate_2 = Resource \u201c{1}\u201d is not part of aggregate \u201c{0}\u201d. +NotAWritableFeatureSet_1 = Resource \u201c{0}\u201d is not a writable feature set. ProcessingExecutedOn_1 = Processing executed on {0}. +ResourceAlreadyExists_1 = A resource already exists at \u201c{0}\u201d. ResourceIdentifierCollision_2 = More than one resource have the \u201c{1}\u201d identifier in the \u201c{0}\u201d data store. ResourceNotFound_2 = No resource found for the \u201c{1}\u201d identifier in the \u201c{0}\u201d data store. ShallBeDeclaredBefore_2 = The \u201c{1}\u201d element must be declared before \u201c{0}\u201d. Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties?rev=1825102&r1=1825101&r2=1825102&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties [ISO-8859-1] (original) +++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties [ISO-8859-1] Thu Feb 22 22:18:05 2018 @@ -25,12 +25,15 @@ # U+00A0 NO-BREAK SPACE before : # AmbiguousName_4 = Le nom \u00ab\u202f{3}\u202f\u00bb est ambigu\u00eb car il peut \u00eatre interpr\u00e9t\u00e9 aussi bien comme \u00ab\u202f{1}\u202f\u00bb ou \u00ab\u202f{2}\u202f\u00bb dans le contexte des donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb. -CanNotGetCommonMetadata_2 = Ne peut pas obtenir les m\u00e9ta-donn\u00e9es communes aux fichiers \u00ab\u202f{0}\u202f\u00bb. Raison\u2008: {1} +CanNotCreateFolderStore_1 = Ne peut pas cr\u00e9er des ressources bas\u00e9es sur le contenu du r\u00e9pertoire \u00ab\u202f{0}\u202f\u00bb. +CanNotGetCommonMetadata_2 = Ne peut pas obtenir les m\u00e9ta-donn\u00e9es communes aux fichiers \u00ab\u202f{0}\u202f\u00bb. La raison est\u2008: {1} CanNotReadCRS_WKT_1 = Ne peut pas lire le syst\u00e8me de r\u00e9f\u00e9rence spatial dans le \u00ab\u202fWell Known Text\u202f\u00bb (WKT) de \u00ab\u202f{0}\u202f\u00bb. CanNotReadDirectory_1 = Ne peut pas lire le r\u00e9pertoire \u00ab\u202f{0}\u202f\u00bb. CanNotReadFile_2 = Ne peut pas lire \u00ab\u202f{1}\u202f\u00bb comme un fichier au format {0}. CanNotReadFile_3 = Ne peut pas lire la ligne {2} de \u00ab\u202f{1}\u202f\u00bb comme une partie d\u2019un fichier au format {0}. CanNotReadFile_4 = Ne peut pas lire la ligne {2} (apr\u00e8s la colonne {3}) de \u00ab\u202f{1}\u202f\u00bb comme une partie d\u2019un fichier au format {0}. +CanNotRemoveResource_2 = Ne peut pas supprimer la ressource \u00ab\u202f{1}\u202f\u00bb de l\u2019agr\u00e9gat \u00ab\u202f{0}\u202f\u00bb. +CanNotStoreResourceType_2 = Ne peut pas enregistrer des ressources de type \u2018{1}\u2019 dans un entrep\u00f4t de donn\u00e9es \u00ab\u202f{0}\u202f\u00bb. ClosedReader_1 = Ce lecteur {0} est ferm\u00e9. ClosedWriter_1 = Cet encodeur {0} est ferm\u00e9. ConcurrentRead_1 = Une ou plusieurs op\u00e9rations de lecture sont en cours sur les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb. @@ -40,17 +43,24 @@ DataStoreLocale = Conv DataStoreLocation = Chemin (fichier ou URL) vers la source de donn\u00e9es. DataStoreTimeZone = Fuseau horaire des dates dans les donn\u00e9es. DirectoryContent_1 = Contenu du r\u00e9pertoire \u00ab\u202f{0}\u202f\u00bb. +DirectoryContentFormatName = Nom du format ou de la source de donn\u00e9es \u00e0 utiliser pour lire ou \u00e9crire le contenu du r\u00e9pertoire. ExcessiveStringSize_3 = La cha\u00eene de caract\u00e8res dans le fichier \u00ab\u202f{0}\u202f\u00bb est trop longue. La cha\u00eene fait {2} caract\u00e8res alors que la limite est {1}. FeatureAlreadyPresent_2 = Une entit\u00e9 nomm\u00e9e \u00ab\u202f{1}\u202f\u00bb est d\u00e9j\u00e0 pr\u00e9sente dans les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb. FeatureNotFound_2 = L\u2019entit\u00e9 \u00ab\u202f{1}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9e dans les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb. -FolderStoreProviderParameter = Nom du format ou de la source de donn\u00e9es \u00e0 utiliser pour lire ou \u00e9crire le contenu du r\u00e9pertoire. +FileAlreadyExists_2 = Un {1,choice,0#fichier|1#r\u00e9pertoire} existe d\u00e9j\u00e0 \u00e0 l\u2019emplacement \u00ab\u202f{0}\u202f\u00bb. +FileIsNotAResourceDirectory_1 = Le fichier \u00ab\u202f{0}\u202f\u00bb n\u2019est pas un r\u00e9pertoire de ressources. FoliationRepresentation = Indique s\u2019il faut assembler les fragments de trajectoires (lignes dans un fichier CSV) dans une entit\u00e9 unique. IllegalFeatureType_2 = Le format {0} ne stocke pas de donn\u00e9es de type \u00ab\u202f{1}\u202f\u00bb. IllegalInputTypeForReader_2 = Le lecteur {0} n\u2019accepte pas des entr\u00e9s de type \u2018{1}\u2019. IllegalOutputTypeForWriter_2 = L\u2019encodeur {0} n\u2019accepte pas des sorties de type \u2018{1}\u2019. InconsistentNameComponents_2 = Les \u00e9l\u00e9ments qui composent le nom \u00ab\u202f{1}\u202f\u00bb ne sont pas coh\u00e9rents avec ceux du nom qui avait \u00e9t\u00e9 pr\u00e9c\u00e9demment li\u00e9 dans les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb. +MissingResourceIdentifier_1 = La ressource \u00ab\u202f{0}\u202f\u00bb n\u2019a pas d\u2019identifiant. MissingSchemeInURI_1 = Il manque le sch\u00e9ma dans l\u2019URI \u00ab\u202f{0}\u202f\u00bb. +NoSuchResourceDirectory_1 = Aucun r\u00e9pertoire de ressources n\u2019a \u00e9t\u00e9 trouv\u00e9 \u00e0 l\u2019emplacement \u00ab\u202f{0}\u202f\u00bb. +NoSuchResourceInAggregate_2 = La ressource \u00ab\u202f{1}\u202f\u00bb n\u2019est pas une partie de l\u2019agr\u00e9gat \u00ab\u202f{0}\u202f\u00bb. +NotAWritableFeatureSet_1 = La ressource \u00ab\u202f{0}\u202f\u00bb n\u2019est pas un ensemble d\u2019entit\u00e9s accessibles en \u00e9criture. ProcessingExecutedOn_1 = Traitement ex\u00e9cut\u00e9 sur {0}. +ResourceAlreadyExists_1 = Une ressource existe d\u00e9j\u00e0 \u00e0 l\u2019emplacement \u00ab\u202f{0}\u202f\u00bb. ResourceIdentifierCollision_2 = Plusieurs ressources utilisent l\u2019identifiant \u00ab\u202f{1}\u202f\u00bb dans les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb. ResourceNotFound_2 = Aucune ressource n\u2019a \u00e9t\u00e9 trouv\u00e9e pour l\u2019identifiant \u00ab\u202f{1}\u202f\u00bb dans les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb. ShallBeDeclaredBefore_2 = L\u2019\u00e9l\u00e9ment \u00ab\u202f{1}\u202f\u00bb doit \u00eatre d\u00e9clar\u00e9 avant \u00ab\u202f{0}\u202f\u00bb. Added: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java?rev=1825102&view=auto ============================================================================== --- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java (added) +++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java [UTF-8] Thu Feb 22 22:18:05 2018 @@ -0,0 +1,227 @@ +/* + * 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.internal.storage; + +import java.util.EnumSet; +import java.nio.file.OpenOption; +import java.nio.file.StandardOpenOption; +import java.util.stream.Stream; +import org.opengis.metadata.Metadata; +import org.opengis.metadata.identification.Identification; +import org.opengis.metadata.identification.DataIdentification; +import org.apache.sis.util.Static; +import org.apache.sis.storage.FeatureSet; +import org.apache.sis.storage.Resource; +import org.apache.sis.storage.DataStore; +import org.apache.sis.storage.DataStoreProvider; +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.storage.WritableFeatureSet; +import org.apache.sis.internal.util.Citations; +import org.apache.sis.util.Classes; + +// Branch-dependent imports +import org.opengis.feature.Feature; + + +/** + * Utility methods related to {@link DataStore}s, {@link DataStoreProvider}s and {@link Resource}s. + * This is not a commit API; any method in this class may change in any future Apache SIS version. + * Some methods may also move in public API if we feel confident enough. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.0 + * @since 1.0 + * @module + */ +public final class StoreUtilities extends Static { + /** + * Do not allow instantiation of this class. + */ + private StoreUtilities() { + } + + /** + * Returns an identifier for the given data store provider, or {@code null} if none. + * The data store identifier should be the format name, but this is not guaranteed. + * It current version, it is not even guaranteed to be unique. + * + * <p>This method will need to be revisited since {@link DataStoreProvider#getShortName()} said that + * the short name is not to be used as an identifier. In the meantime, we use this method as a way + * to keep trace of the location in the code where an identifier is desired.</p> + * + * @param provider the provider for which to get an identifier, or {@code null}. + * @return an identifier for the given data store, or {@code null}. + */ + public static String getIdentifier(final DataStoreProvider provider) { + return (provider != null) ? provider.getShortName() : null; + } + + /** + * Returns an identifier for a resource having the given metadata, or {@code null} if none. + * This method checks the information returned by {@link Metadata#getIdentificationInfo()}, + * with precedence to {@link DataIdentification} over other kinds of {@link Identification}. + * + * @param metadata the metadata from which to get a data identifier, or {@code null}. + * @return a data identifier, or {@code null} if none. + */ + public static String getIdentifier(final Metadata metadata) { + return Citations.removeIgnorableCharacters(getIdentifier(metadata, true)); + } + + /** + * Implementation of {@link #getIdentifier(Metadata)} to be shared with {@link #getLabel(Resource)}. + * + * @param metadata the metadata from which to get a data identifier, or {@code null}. + * @param unicode whether to restrict to valid Unicode identifiers. + * @return a data identifier, or {@code null} if none. + */ + private static String getIdentifier(final Metadata metadata, final boolean unicode) { + String fallback = null; + if (metadata != null) { + for (final Identification md : metadata.getIdentificationInfo()) { + String id = Citations.getIdentifier(md.getCitation(), unicode); + if (id != null) { + if (md instanceof DataIdentification) { + return id; + } else if (fallback == null) { + fallback = id; + } + } + } + } + return fallback; + } + + /** + * Returns a short label for the given resource. This method returns an identifier if possible, + * or the title otherwise. If neither an identifier or title can be found, then this method returns + * the kind of resource implemented by the given object. + * + * @param resource the resource for which to get a label. + * @return a human-readable label for the given resource (not to be used as an identifier). + * @throws DataStoreException if an error occurred while fetching metadata. + */ + public static String getLabel(final Resource resource) throws DataStoreException { + String title = null; + if (resource instanceof DataStore) { + title = ((DataStore) resource).getDisplayName(); + } + if (title == null) { + title = getIdentifier(resource.getMetadata(), false); + if (title == null) { + title = Classes.getShortName(getInterface(resource.getClass())); + } + } + return title; + } + + /** + * Returns the most specific interface implemented by the given class. + * For indicative purpose only, as this method has arbitrary behavior if more than one leaf is found. + * + * @param implementation the implementation class. + * @return the most specific resource interface. + */ + public static Class<? extends Resource> getInterface(final Class<? extends Resource> implementation) { + final Class<? extends Resource>[] types = Classes.getLeafInterfaces(implementation, Resource.class); + Class<? extends Resource> type = null; + for (int i=types.length; --i >= 0;) { + type = types[i]; + if (FeatureSet.class.isAssignableFrom(type)) break; // Arbitrary precedence rule. + } + return type; // Should never be null since the 'types' array should never be empty. + } + + /** + * Converts the given sequence of options into a simplified set of standard options. + * The returned set can contain combinations of + * {@link StandardOpenOption#WRITE}, + * {@link StandardOpenOption#CREATE CREATE}, + * {@link StandardOpenOption#CREATE_NEW CREATE_NEW} and + * {@link StandardOpenOption#TRUNCATE_EXISTING TRUNCATE_EXISTING}. + * If the set is empty, then the data store should be read-only. + * If both {@code TRUNCATE_EXISTING} and {@code CREATE_NEW} are specified, + * then {@code CREATE_NEW} has precedence. + * More specifically: + * + * <p>{@link StandardOpenOption#WRITE}<br> + * means that the {@link DataStore} should be opened as writable resource.</p> + * + * <p>{@link StandardOpenOption#CREATE}<br> + * means that the {@link DataStore} is allowed to create new files. + * If this option is present, then {@code WRITE} is also present. + * If this option is absent, then writable data stores should not create any new file. + * This flag can be tested as below (this cover both the read-only case and the writable + * case where the files must exist):</p> + * + * {@preformat java + * if (!options.contains(StandardOpenOption.CREATE)) { + * // Throw an exception if the file does not exist. + * } + * } + * + * <p>{@link StandardOpenOption#CREATE_NEW}<br> + * means that the {@link DataStore} should fail to open if the file already exists. + * This mode is used when creating new writable resources, for making sure that we + * do not modify existing resources. + * If this option is present, then {@code WRITE} and {@code CREATE} are also present.</p> + * + * <p>{@link StandardOpenOption#TRUNCATE_EXISTING}<br> + * means that the {@link DataStore} should overwrite the content of any pre-existing resources. + * If this option is present, then {@code WRITE} and {@code CREATE} are also present.</p> + * + * @param options the open options, or {@code null}. + * @return the open options as a bitmask. + */ + @SuppressWarnings("fallthrough") + public static EnumSet<StandardOpenOption> toStandardOptions(final OpenOption[] options) { + final EnumSet<StandardOpenOption> set = EnumSet.noneOf(StandardOpenOption.class); + if (options != null) { + for (final OpenOption op : options) { + if (op instanceof StandardOpenOption) { + switch ((StandardOpenOption) op) { // Fallthrough in every cases. + case CREATE_NEW: set.add(StandardOpenOption.CREATE_NEW); + case TRUNCATE_EXISTING: set.add(StandardOpenOption.TRUNCATE_EXISTING); + case CREATE: set.add(StandardOpenOption.CREATE); + case APPEND: case WRITE: set.add(StandardOpenOption.WRITE); + } + } + } + if (set.contains(StandardOpenOption.CREATE_NEW)) { + set.remove(StandardOpenOption.TRUNCATE_EXISTING); + } + } + return set; + } + + /** + * Copies all feature from the given source to the given target. + * We use this method as central point where such copy occur, in case we want to implement + * a more efficient algorithm in some future Apache SIS version. For example we could copy + * the files using {@link java.nio.file.Files} if we determine that it is possible. + * + * @param source the source set of features. + * @param target where to copy the features. + * @throws DataStoreException if an error occurred during the copy operation. + */ + public static void copy(final FeatureSet source, final WritableFeatureSet target) throws DataStoreException { + target.updateType(source.getType()); + try (Stream<Feature> stream = source.features(false)) { + target.add(stream.iterator()); + } + } +} Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java ------------------------------------------------------------------------------ svn:mime-type = text/plain;charset=UTF-8 Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java?rev=1825102&r1=1825101&r2=1825102&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java [UTF-8] (original) +++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java [UTF-8] Thu Feb 22 22:18:05 2018 @@ -165,7 +165,8 @@ public abstract class URIDataStore exten * strings, without the leading dot.</div> * * The suffixes are case-insensitive (no need to declare both lower-case and upper-case variants) - * and shall not contain the leading dot. + * and shall not contain the leading dot. The first element in the list is the preferred suffix + * to use for new files. * * @return the filename suffixes, case insensitive. Never null but can be empty. */ Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java?rev=1825102&r1=1825101&r2=1825102&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java [UTF-8] (original) +++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java [UTF-8] Thu Feb 22 22:18:05 2018 @@ -16,13 +16,17 @@ */ package org.apache.sis.internal.storage.folder; -import java.io.IOException; +import java.util.EnumSet; import java.util.Locale; import java.util.TimeZone; +import java.io.IOException; import java.nio.charset.Charset; -import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Files; +import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileSystemNotFoundException; +import java.nio.file.NoSuchFileException; +import java.nio.file.StandardOpenOption; import org.opengis.util.InternationalString; import org.opengis.parameter.ParameterValueGroup; import org.opengis.parameter.ParameterDescriptor; @@ -57,7 +61,7 @@ public final class FolderStoreProvider e /** * A short name or abbreviation for the data format. */ - private static final String NAME = "folder"; + static final String NAME = "folder"; /** * Description of the parameter for formating conventions of dates and numbers. @@ -91,7 +95,7 @@ public final class FolderStoreProvider e ENCODING = annotate(builder, URIDataStore.Provider.ENCODING, remark); LOCALE = builder.addName("locale" ).setDescription(Resources.formatInternational(Resources.Keys.DataStoreLocale )).setRemarks(remark).create(Locale.class, null); TIMEZONE = builder.addName("timezone").setDescription(Resources.formatInternational(Resources.Keys.DataStoreTimeZone)).setRemarks(remark).create(TimeZone.class, null); - FORMAT = builder.addName("provider").setDescription(Resources.formatInternational(Resources.Keys.FolderStoreProviderParameter)).create(String.class, null); + FORMAT = builder.addName("format" ).setDescription(Resources.formatInternational(Resources.Keys.DirectoryContentFormatName)).create(String.class, null); location = new ParameterBuilder(URIDataStore.Provider.LOCATION_PARAM).create(Path.class, null); PARAMETERS = builder.addName(NAME).createGroup(location, LOCALE, TIMEZONE, ENCODING, FORMAT); } @@ -170,12 +174,73 @@ public final class FolderStoreProvider e */ @Override public DataStore open(final StorageConnector connector) throws DataStoreException { + return open(connector, null, EnumSet.noneOf(StandardOpenOption.class)); + } + + /** + * Shared implementation of public {@code open(…)} methods. + * + * @param connector information about the storage (URL, path, <i>etc</i>). + * @param format format name, or {@code null} if unspecified. + * @param options whether to create a new directory, overwrite existing content, <i>etc</i>. + */ + private DataStore open(final StorageConnector connector, final String format, final EnumSet<StandardOpenOption> options) + throws DataStoreException + { + Path path = null; + final Store store; try { - return new Store(this, connector, null); + /* + * If the user asked to create a new directory, we need to perform this task before + * to create the Store (otherwise constructor will fail with NoSuchFileException). + * In the particular case of CREATE_NEW, we unconditionally attempt to create the + * directory in order to rely on the atomic check performed by Files.createDirectory(…). + */ + if (options.contains(StandardOpenOption.CREATE)) { + path = connector.getStorageAs(Path.class); + if (options.contains(StandardOpenOption.CREATE_NEW) || Files.notExists(path)) { + Files.createDirectory(path); // IOException if the directory already exists. + } + } + // TODO: check also if @Capabilities.values().contains(WRITE). + if (options.contains(StandardOpenOption.WRITE)) { + store = new WritableStore(this, connector, format); // May throw NoSuchFileException. + } else { + store = new Store(this, connector, format); // May throw NoSuchFileException. + } + /* + * If there is a destructive operation to perform (TRUNCATE_EXISTING), do it last only + * after we have successfully created the data store. The check for directory existence + * is also done after creation to be sure to check the path used by the store. + */ + path = store.location; + if (!Files.isDirectory(path)) { + throw new DataStoreException(Resources.format(Resources.Keys.FileIsNotAResourceDirectory_1, path)); + } + if (options.contains(StandardOpenOption.TRUNCATE_EXISTING)) { + WritableStore.deleteRecursively(path, false); + } } catch (IOException e) { - throw new DataStoreException(Resources.format(Resources.Keys.CanNotReadDirectory_1, - connector.getStorageName()), e); + /* + * In case of error, Java FileSystem implementation tries to throw a specific exception + * (NoSuchFileException or FileAlreadyExistsException), but this is not guaranteed. + */ + int isDirectory = 0; + final short errorKey; + if (e instanceof FileAlreadyExistsException) { + if (path != null && Files.isDirectory(path)) { + isDirectory = 1; + } + errorKey = Resources.Keys.FileAlreadyExists_2; + } else if (e instanceof NoSuchFileException) { + errorKey = Resources.Keys.NoSuchResourceDirectory_1; + } else { + errorKey = Resources.Keys.CanNotCreateFolderStore_1; + } + throw new DataStoreException(Resources.format(errorKey, + (path != null) ? path : connector.getStorageName(), isDirectory), e); } + return store; } /** @@ -193,16 +258,11 @@ public final class FolderStoreProvider e connector.setOption(OptionKey.TIMEZONE, pg.getValue(TIMEZONE)); connector.setOption(OptionKey.ENCODING, pg.getValue(ENCODING)); final String format = pg.getValue(FORMAT); - try { - if (format != null) { - return new Store.Writable(this, connector, format); - } else { - return new Store(this, connector, format); - } - } catch (IOException e) { - throw new DataStoreException(Resources.format(Resources.Keys.CanNotReadDirectory_1, - connector.getStorageName()), e); + final EnumSet<StandardOpenOption> options = EnumSet.noneOf(StandardOpenOption.class); + if (format != null) { + options.add(StandardOpenOption.WRITE); } + return open(connector, format, options); } /** Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java?rev=1825102&r1=1825101&r2=1825102&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java [UTF-8] (original) +++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java [UTF-8] Thu Feb 22 22:18:05 2018 @@ -22,6 +22,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Locale; import java.util.TimeZone; +import java.util.logging.Level; +import java.util.concurrent.ConcurrentHashMap; import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.Files; @@ -29,17 +31,9 @@ import java.nio.file.DirectoryStream; import java.nio.file.DirectoryIteratorException; import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.file.FileVisitResult; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Map.Entry; -import java.util.logging.Level; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Stream; import org.opengis.metadata.Metadata; import org.opengis.metadata.maintenance.ScopeCode; import org.opengis.parameter.ParameterValueGroup; -import org.opengis.metadata.identification.Identification; import org.apache.sis.setup.OptionKey; import org.apache.sis.storage.Resource; import org.apache.sis.storage.Aggregate; @@ -52,20 +46,10 @@ import org.apache.sis.storage.Unsupporte import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.internal.util.UnmodifiableArrayList; import org.apache.sis.internal.storage.MetadataBuilder; +import org.apache.sis.internal.storage.StoreUtilities; import org.apache.sis.internal.storage.Resources; -import org.apache.sis.internal.storage.URIDataStore; -import org.apache.sis.internal.storage.FileSystemResource; -import org.apache.sis.metadata.iso.citation.Citations; -import org.apache.sis.storage.FeatureSet; -import org.apache.sis.storage.ProbeResult; -import org.apache.sis.storage.ReadOnlyStorageException; -import org.apache.sis.storage.WritableAggregate; -import org.apache.sis.storage.WritableFeatureSet; import org.apache.sis.util.resources.Errors; -// Branch-dependent imports -import org.opengis.feature.Feature; - /** * A folder store acts as an aggregate of multiple files in a single store. @@ -91,23 +75,6 @@ import org.opengis.feature.Feature; */ class Store extends DataStore implements Aggregate, DirectoryStream.Filter<Path> { /** - * File walker to delete file and folder recursively. - */ - private static final SimpleFileVisitor<Path> FILE_DELETE = new SimpleFileVisitor<Path>() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.delete(file); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - Files.delete(dir); - return FileVisitResult.CONTINUE; - } - }; - - /** * The {@link FolderStoreProvider#LOCATION} parameter value, or {@code null} if none. */ protected final Path location; @@ -128,15 +95,10 @@ class Store extends DataStore implements protected final Charset encoding; /** - * Single provider to use in searches and creation operations, or {@code null} if unspecified. - */ - protected final String providerName; - - /** * All data stores (including sub-folders) found in the directory structure, including the root directory. * This is used for avoiding never-ending loop with symbolic links. */ - protected final Map<Path,DataStore> children; + final Map<Path,DataStore> children; /** * Information about the data store as a whole, created when first needed. @@ -150,28 +112,36 @@ class Store extends DataStore implements * * @see #components() */ - protected transient Collection<Resource> components; + transient Collection<Resource> components; /** - * {@code true} if {@link #sharedRepository(Path)} has already been invoked for {@link #location} path. - * This is used for avoiding to report the same message many times. + * The provider to use for probing the directory content, opening files and creating new files. + * The provider is determined by the format name specified at construction time. + * This field is {@code null} if that format name is null. */ - private transient boolean sharedRepositoryReported; + protected final DataStoreProvider componentProvider; /** - * Cached search and create provider to use. + * {@code true} if {@link #sharedRepository(Path)} has already been invoked for {@link #location} path. + * This is used for avoiding to report the same message many times. */ - private transient DataStoreProvider searchProvider; + private transient boolean sharedRepositoryReported; /** * Creates a new folder store from the given file, path or URI. + * The folder store will attempt to open only the files of the given format, if non-null. + * If a null format name is specified, then the folder store will attempt to open any file + * found in the directory (this may produce confusing results). * * @param provider the factory that created this {@code DataStore} instance, or {@code null} if unspecified. * @param connector information about the storage (URL, stream, <i>etc</i>). - * @throws DataStoreException if an error occurred while opening the stream. + * @param format name of the format to use for reading or writing the directory content, or {@code null}. + * @throws UnsupportedStorageException if the given format name is unknown. + * @throws DataStoreException if an error occurred while fetching the directory {@link Path}. + * @throws IOException if an error occurred while using the directory {@code Path}. */ @SuppressWarnings("ThisEscapedInObjectConstruction") // Okay because 'folders' does not escape. - Store(final DataStoreProvider provider, final StorageConnector connector, final String format) + Store(final DataStoreProvider provider, final StorageConnector connector, String format) throws DataStoreException, IOException { super(provider, connector); @@ -179,9 +149,20 @@ class Store extends DataStore implements locale = connector.getOption(OptionKey.LOCALE); timezone = connector.getOption(OptionKey.TIMEZONE); encoding = connector.getOption(OptionKey.ENCODING); - providerName = format; children = new ConcurrentHashMap<>(); children.put(location.toRealPath(), this); + if (format == null) { + componentProvider = null; + } else { + format = format.trim(); + for (DataStoreProvider cp : DataStores.providers()) { + if (format.equalsIgnoreCase(StoreUtilities.getIdentifier(cp))) { + componentProvider = cp; + return; + } + } + throw new UnsupportedStorageException(Errors.getResources(super.getLocale()).getString(Errors.Keys.UnsupportedFormat_1, format)); + } } /** @@ -193,13 +174,12 @@ class Store extends DataStore implements */ private Store(final Store parent, final StorageConnector connector) throws DataStoreException { super(parent, connector); - location = connector.getStorageAs(Path.class); - locale = connector.getOption(OptionKey.LOCALE); - timezone = connector.getOption(OptionKey.TIMEZONE); - encoding = connector.getOption(OptionKey.ENCODING); - children = parent.children; - providerName = parent.providerName; - searchProvider = parent.searchProvider; + location = connector.getStorageAs(Path.class); + locale = connector.getOption(OptionKey.LOCALE); + timezone = connector.getOption(OptionKey.TIMEZONE); + encoding = connector.getOption(OptionKey.ENCODING); + children = parent.children; + componentProvider = parent.componentProvider; } /** @@ -207,12 +187,13 @@ class Store extends DataStore implements */ @Override public ParameterValueGroup getOpenParameters() { + final String format = StoreUtilities.getIdentifier(componentProvider); final ParameterValueGroup pg = (provider != null ? provider.getOpenParameters() : FolderStoreProvider.PARAMETERS).createValue(); pg.parameter(DataStoreProvider.LOCATION).setValue(location); - if (locale != null) pg.parameter("locale" ).setValue(locale ); - if (timezone != null) pg.parameter("timezone").setValue(timezone); - if (encoding != null) pg.parameter("encoding").setValue(encoding); - if (providerName != null) pg.parameter("provider").setValue(providerName); + if (locale != null) pg.parameter("locale" ).setValue(locale ); + if (timezone != null) pg.parameter("timezone").setValue(timezone); + if (encoding != null) pg.parameter("encoding").setValue(encoding); + if (format != null) pg.parameter("format" ).setValue(format); return pg; } @@ -253,8 +234,8 @@ class Store extends DataStore implements @SuppressWarnings("ReturnOfCollectionOrArrayField") public synchronized Collection<Resource> components() throws DataStoreException { if (components == null) { + final List<DataStore> resources = new ArrayList<>(); try (DirectoryStream<Path> stream = Files.newDirectoryStream(location, this)) { - final List<DataStore> resources = new ArrayList<>(); for (final Path candidate : stream) { /* * The candidate path may be a symbolic link to a file that we have previously read. @@ -278,20 +259,17 @@ class Store extends DataStore implements connector.setOption(OptionKey.LOCALE, locale); connector.setOption(OptionKey.TIMEZONE, timezone); connector.setOption(OptionKey.ENCODING, encoding); - - final DataStoreProvider provider = getSearchAndCreateProvider(); try { - if (provider != null) { - final ProbeResult result = provider.probeContent(connector); - if (result.isSupported()) { - next = provider.open(connector); - } else { - throw new UnsupportedStorageException(); - } + if (componentProvider == null) { + next = DataStores.open(connector); // May throw UnsupportedStorageException. + } else if (componentProvider.probeContent(connector).isSupported()) { + next = componentProvider.open(connector); // Open a file of specified format. + } else if (Files.isDirectory(candidate)) { + next = new Store(this, connector); // Open a sub-directory. } else { - next = DataStores.open(connector); + connector.closeAllExcept(null); // Not the format specified at construction time. + continue; } - } catch (UnsupportedStorageException ex) { if (!Files.isDirectory(candidate)) { connector.closeAllExcept(null); @@ -322,7 +300,6 @@ class Store extends DataStore implements } resources.add(next); } - components = UnmodifiableArrayList.wrap(resources.toArray(new Resource[resources.size()])); } catch (DirectoryIteratorException | UncheckedIOException ex) { // The cause is an IOException (no other type allowed). throw new DataStoreException(canNotRead(), ex.getCause()); @@ -331,31 +308,12 @@ class Store extends DataStore implements } catch (BackingStoreException ex) { throw ex.unwrapOrRethrow(DataStoreException.class); } + components = UnmodifiableArrayList.wrap(resources.toArray(new Resource[resources.size()])); } return components; // Safe because unmodifiable list. } /** - * - * @return search and create provider, can be null - * @throws DataStoreException - */ - protected DataStoreProvider getSearchAndCreateProvider() throws DataStoreException { - if (searchProvider == null && providerName != null) { - for (DataStoreProvider provider : DataStores.providers()) { - if (providerName.equals(provider.getShortName())) { - searchProvider = provider; - break; - } - } - if (searchProvider == null) { - throw new DataStoreException(Errors.getResources(getLocale()).getString(Errors.Keys.UnsupportedFormat_1, providerName)); - } - } - return searchProvider; - } - - /** * Builds an error message for an error occurring while reading files in the directory. */ private String canNotRead() { @@ -381,7 +339,7 @@ class Store extends DataStore implements * * @param key one of the {@link Resources.Keys} constants ending with {@code _1} suffix. */ - private String message(final short key, final Object value) { + final String message(final short key, final Object value) { return Resources.forLocale(getLocale()).getString(key, value); } @@ -410,160 +368,4 @@ class Store extends DataStore implements } } } - - /** - * Writable version of the store which rely on given datastore provider to create new types. - * - * Note 1 : this implementation is experimental. - * Note 2 : it has not been tested since we do not have writable feature sets yet. - */ - static class Writable extends Store implements WritableAggregate { - - public Writable(final DataStoreProvider provider, final StorageConnector connector, final String format) - throws DataStoreException, IOException - { - super(provider, connector, format); - } - - /** - * Create a new resource. - * This implementation uses the provider given in store creation parameters. - * - * @param resource - * @return - * @throws DataStoreException - */ - @Override - public synchronized Resource add(Resource resource) throws DataStoreException { - if (!(resource instanceof FeatureSet)) { - throw new DataStoreException("Only FeatureSet resources can be imported in this store."); - } - - if (components().contains(resource)) { - throw new DataStoreException("Resource is already in this aggregate."); - } - - //we know it is not null in this instance - final DataStoreProvider provider = getSearchAndCreateProvider(); - if (!(provider instanceof URIDataStore.Provider)) { - throw new DataStoreException("Resource creation is possible only with URIProviders"); - } - - final URIDataStore.Provider p = (URIDataStore.Provider) provider; - - //build location - String fileName = null; - for (Identification id : resource.getMetadata().getIdentificationInfo()) { - fileName = Citations.getIdentifier(id.getCitation()); - if (fileName!=null && !fileName.isEmpty()) break; - } - if (fileName == null || fileName.isEmpty()) { - throw new DataStoreException("Resource does not have an identifier."); - } - - //some format may have no suffix at all - if (!p.getSuffix().isEmpty()) { - fileName += "."+ p.getSuffix().get(0); - } - - //create new store/resource - final Path location = this.location.resolve(fileName); - final StorageConnector connector = new StorageConnector(location); - connector.setOption(OptionKey.LOCALE, locale); - connector.setOption(OptionKey.TIMEZONE, timezone); - connector.setOption(OptionKey.ENCODING, encoding); - final DataStore store = p.open(connector); - - //check we can write datas - if (!(store instanceof WritableFeatureSet)) { - try { - //remove any created file - if (resource instanceof FileSystemResource) { - //delete resource files - final Path[] resourcePaths = ((FileSystemResource) resource).getResourcePaths(); - for (Path path : resourcePaths) { - Files.walkFileTree(path, FILE_DELETE); - } - } - Files.deleteIfExists(location); - } catch (IOException ex) { - //do nothing - } finally { - store.close(); - } - throw new DataStoreException("Created resource is not a WritableFeatureSet."); - } - - //copy datas between resources - children.put(location, store); - final FeatureSet source = (FeatureSet) resource; - final WritableFeatureSet target = (WritableFeatureSet) store; - target.updateType(source.getType()); - try (Stream<Feature> stream = source.features(false)) { - target.add(stream.iterator()); - } - - - //clear cache - components = null; - - return store; - } - - /** - * Note : in this implementation we clear the cache after closing the stores and before deleting the files. - * This ensure in the worse case scenario a new store will be created on the possible remaining files. - * - * @param resource - * @throws ReadOnlyStorageException - * @throws DataStoreException - */ - @Override - public synchronized void remove(Resource resource) throws ReadOnlyStorageException, DataStoreException { - if (!(components().contains(resource))) { - throw new DataStoreException("Unknown resource, verify it is part of this aggregate."); - } - - //clear cache - components = null; - - if (resource instanceof Store) { - final Store store = (Store) resource; - store.close(); - //clear cache - children.remove(store.location); - - try { - Files.walkFileTree(store.location, FILE_DELETE); - } catch (IOException ex) { - throw new DataStoreException(ex.getMessage(), ex); - } - } else { - //resource is a datastore, we are sure of it - final DataStore store = (DataStore) resource; - store.close(); - - //clear cache, we need to do this loop in case the resource is - //not a FileSystemResource or wrongly declares the used files - for (Entry<Path,DataStore> entry : children.entrySet()) { - if (entry.getValue() == store) { - children.remove(entry.getKey()); - break; - } - } - - if (resource instanceof FileSystemResource) { - //delete resource files - final Path[] resourcePaths = ((FileSystemResource) resource).getResourcePaths(); - for (Path path : resourcePaths) { - try { - Files.walkFileTree(path, FILE_DELETE); - } catch (IOException ex) { - throw new DataStoreException(ex.getMessage(), ex); - } - } - } - } - } - } }