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);
- }
- }
- }
- }
- }
- }
}