Copied: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/WritableStore.java (from r1824938, 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/WritableStore.java?p2=sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/WritableStore.java&p1=sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java&r1=1824938&r2=1825102&rev=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/WritableStore.java [UTF-8] Thu Feb 22 22:18:05 2018 @@ -16,554 +16,223 @@ */ package org.apache.sis.internal.storage.folder; -import java.util.Map; -import java.util.List; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Locale; -import java.util.TimeZone; -import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.Files; -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.StandardOpenOption; 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 java.io.IOException; +import java.util.Iterator; import org.apache.sis.setup.OptionKey; import org.apache.sis.storage.Resource; -import org.apache.sis.storage.Aggregate; import org.apache.sis.storage.DataStore; -import org.apache.sis.storage.DataStores; import org.apache.sis.storage.DataStoreProvider; import org.apache.sis.storage.StorageConnector; import org.apache.sis.storage.DataStoreException; -import org.apache.sis.storage.UnsupportedStorageException; -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.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; +import org.apache.sis.internal.storage.StoreUtilities; +import org.apache.sis.internal.storage.FileSystemProvider; +import org.apache.sis.internal.storage.FileSystemResource; +import org.apache.sis.internal.storage.Resources; +import org.apache.sis.util.ArgumentChecks; /** - * A folder store acts as an aggregate of multiple files in a single store. - * Only visible files are considered; all hidden files are excluded. - * Each visible file will be tested and eventually opened by another store. - * This approach allows to discover the content of a folder or archive without - * testing each file one by one. + * Writable version of the store which rely on given datastore provider to create new types. * - * <p><b>Limitations:</b></p> - * <ul> - * <li>Current version is read-only.</li> - * <li>Current version does not watch for external modifications in directory content.</li> - * <li>Current version open all files in the directory and keep those files open. - * If the directory is large, it will be a problem.</li> - * <li>We could open data stores concurrently. This is not yet done.</li> - * </ul> + * Note 1: this implementation is experimental. + * Note 2: it has not been tested since we do not have writable feature sets yet. * * @author Johann Sorel (Geomatys) - * @author Martin Desruisseaux (Geomatys) * @version 1.0 - * @since 0.8 + * @since 1.0 * @module */ -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; - - /** - * Formating conventions of dates and numbers, or {@code null} if unspecified. - */ - protected final Locale locale; - - /** - * Timezone of dates in the data store, or {@code null} if unspecified. - */ - protected final TimeZone timezone; - - /** - * Character encoding used by the data store, or {@code null} if unspecified. - */ - 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; - - /** - * Information about the data store as a whole, created when first needed. - * - * @see #getMetadata() - */ - private transient Metadata metadata; - - /** - * Resources in the folder given at construction time, created when first needed. - * - * @see #components() - */ - protected 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. - */ - private transient boolean sharedRepositoryReported; - - /** - * Cached search and create provider to use. - */ - private transient DataStoreProvider searchProvider; - +final class WritableStore extends Store implements WritableAggregate { /** * Creates a new folder store from the given file, path or URI. - * - * @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. + * Contrarily to the {@link Store} parent class, the {@code format} is mandatory for writable stores. */ - @SuppressWarnings("ThisEscapedInObjectConstruction") // Okay because 'folders' does not escape. - Store(final DataStoreProvider provider, final StorageConnector connector, final String format) + WritableStore(final DataStoreProvider provider, final StorageConnector connector, final String format) throws DataStoreException, IOException { - super(provider, connector); - location = connector.getStorageAs(Path.class); - 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); - } - - /** - * Creates a new sub-folder store as a child of the given folder store. - * - * @param parent the parent folder store. - * @param connector information about the storage (URL, stream, <i>etc</i>). - * @throws DataStoreException if an error occurred while opening the stream. - */ - 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; - } - - /** - * Returns the parameters used to open this data store. - */ - @Override - public ParameterValueGroup getOpenParameters() { - 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); - return pg; - } - - /** - * Invoked during iteration for omitting hidden files. - */ - @Override - public boolean accept(final Path entry) throws IOException { - return !Files.isHidden(entry); + super(provider, connector, format); } /** - * Returns information about the data store as a whole. - * Those metadata contains the directory name in the resource title. - * - * @return information about resources in the data store. + * Create a new file for the given resource. + * This implementation uses the provider specified by the format name given at creation time. */ @Override - public synchronized Metadata getMetadata() { - if (metadata == null) { - final MetadataBuilder mb = new MetadataBuilder(); - final String name = getDisplayName(); - mb.addResourceScope(ScopeCode.COLLECTION, Resources.formatInternational(Resources.Keys.DirectoryContent_1, name)); - mb.addLanguage(locale, MetadataBuilder.Scope.RESOURCE); - mb.addEncoding(encoding, MetadataBuilder.Scope.RESOURCE); - mb.addTitleOrIdentifier(name, MetadataBuilder.Scope.ALL); - metadata = mb.build(true); + public synchronized Resource add(final Resource resource) throws DataStoreException { + ArgumentChecks.ensureNonNull("resource", resource); + if (!(resource instanceof FeatureSet)) { + throw new DataStoreException(message(Resources.Keys.CanNotStoreResourceType_2, new Object[] { + FolderStoreProvider.NAME, StoreUtilities.getInterface(resource.getClass()) + })); + } + /* + * Infer a filename from the resource identifier, if one can be found. + * A suffix is added to the filename if available (some formats may have no suffix at all). + */ + String filename = StoreUtilities.getIdentifier(resource.getMetadata()); + if (filename == null) { + throw new DataStoreException(message(Resources.Keys.MissingResourceIdentifier_1, StoreUtilities.getLabel(resource))); + } + if (componentProvider instanceof FileSystemProvider) { + final Iterator<String> suffixes = ((FileSystemProvider) componentProvider).getSuffix().iterator(); + if (suffixes.hasNext()) { + filename += '.' + suffixes.next(); + } } - return metadata; - } - - /** - * Returns all resources found in the folder given at construction time. - * Only the resources recognized by a {@link DataStore} will be included. - * This includes sub-folders. Resources are in no particular order. - */ - @Override - @SuppressWarnings("ReturnOfCollectionOrArrayField") - public synchronized Collection<Resource> components() throws DataStoreException { - if (components == null) { - 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. - * In such case, use the existing data store. A use case is a directory containing - * hundred of GeoTIFF files all accompanied by ".prj" files having identical content. - * (Note: those ".prj" files should be invisible since they should be identified as - * GeoTIFF auxiliary files, but current Store implementation does not know that). - */ - final Path real = candidate.toRealPath(); - DataStore next = children.get(real); - if (next instanceof Store) { - ((Store) next).sharedRepository(real); // Warn about directories only. - } - if (next == null) { - /* - * The candidate file has never been read before. Try to read it now. - * If the file format is unknown (UnsupportedStorageException), we will - * check if we can open it as a child folder store before to skip it. - */ - final StorageConnector connector = new StorageConnector(candidate); - 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(); - } - } else { - next = DataStores.open(connector); - } - - } catch (UnsupportedStorageException ex) { - if (!Files.isDirectory(candidate)) { - connector.closeAllExcept(null); - listeners.warning(Level.FINE, null, ex); - continue; - } - next = new Store(this, connector); - } catch (DataStoreException ex) { - try { - connector.closeAllExcept(null); - } catch (DataStoreException s) { - ex.addSuppressed(s); - } - throw ex; - } - /* - * At this point we got the data store. It could happen that a store for - * the same file has been added concurrently, so we need to check again. - */ - final DataStore existing = children.putIfAbsent(real, next); - if (existing != null) { - next.close(); - next = existing; - if (next instanceof Store) { - ((Store) next).sharedRepository(real); // Warn about directories only. - } + /* + * Create new store/resource for write access, provided that no store already exist for the path. + * We use the CREATE_NEW option in order to intentionally fail if the resource already exists. + */ + final Path path = location.resolve(filename); + if (!children.containsKey(path)) { + final StorageConnector connector = new StorageConnector(path); + connector.setOption(OptionKey.LOCALE, locale); + connector.setOption(OptionKey.TIMEZONE, timezone); + connector.setOption(OptionKey.ENCODING, encoding); + connector.setOption(OptionKey.OPEN_OPTIONS, new StandardOpenOption[] { + StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE + }); + final DataStore store = componentProvider.open(connector); + if (children.putIfAbsent(path, store) == null) { + /* + * Check if we can write data. It should be the case since we specified StandardOpenOption.WRITE. + * Note that if 'componentProvider' can create only read-only store, then open(connector) should + * have failed with NoSuchFileException before we reach this point. + */ + // TODO: handle transactional case. + if (store instanceof WritableFeatureSet) { + StoreUtilities.copy((FeatureSet) resource, (WritableFeatureSet) store); + components = null; // Clear cache. TODO: we should do something more efficient. + return store; + } + /* + * If the data store is not a WritableFeatureSet, current implementation can not use it. + * Delete the file that the store may have created. + * + * TODO: we should set a flag for blocking next attempts to add a resources, since they are likely + * to fail as well. Maybe we should not delete any files since we are not sure to delete the + * right ones. For example store.getComponentPaths() may return a path outside the directory + * managed by this folder store. + */ + final DataStoreException ex = new DataStoreException(Resources.format( + Resources.Keys.NotAWritableFeatureSet_1, store.getDisplayName())); + store.close(); + try { + if (store instanceof Store) { + deleteRecursively(((Store) store).location, true); + } else if (store instanceof FileSystemResource) { + for (Path c : ((FileSystemResource) store).getResourcePaths()) { + Files.delete(c); } } - resources.add(next); + } catch (IOException e) { + ex.addSuppressed(e); } - 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()); - } catch (IOException ex) { - throw new DataStoreException(canNotRead(), ex); - } catch (BackingStoreException ex) { - throw ex.unwrapOrRethrow(DataStoreException.class); + children.remove(path, store); + throw ex; } + store.close(); } - return components; // Safe because unmodifiable list. + throw new DataStoreException(Resources.format(Resources.Keys.ResourceAlreadyExists_1, path)); } /** + * Removes a {@code Resource} from this store. The resource must be a part of this {@code Aggregate}. + * For a folder store, this means that the resource must be a direct children of the directory managed + * by this store. * - * @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() { - return message(Resources.Keys.CanNotReadDirectory_1, getDisplayName()); - } - - /** - * Logs a warning about a file that could be read, but happen to be a directory that we have read previously. - * We could add the existing {@link Aggregate} instance in the parent {@code Aggregate} that we are building, - * but doing so may create a cycle. Current version logs a warning instead because users may not be prepared - * to handle cycles. Not that we have no guarantee that a cycle really exists at this stage, only that it may - * exist. - */ - private void sharedRepository(final Path candidate) { - if (!sharedRepositoryReported) { - sharedRepositoryReported = true; - listeners.warning(message(Resources.Keys.SharedDirectory_1, candidate), null); - } - } - - /** - * Returns a localized string for the given key and value. - * - * @param key one of the {@link Resources.Keys} constants ending with {@code _1} suffix. - */ - private String message(final short key, final Object value) { - return Resources.forLocale(getLocale()).getString(key, value); - } - - /** - * Closes all children resources. + * This operation is destructive: the {@link Resource} and it's related files will be deleted. */ @Override - public synchronized void close() throws DataStoreException { - final Collection<Resource> resources = components; - if (resources != null) { - components = null; // Clear first in case of failure. - DataStoreException failure = null; - for (final Resource r : resources) { - if (r instanceof DataStore) try { - ((DataStore) r).close(); - } catch (DataStoreException ex) { - if (failure == null) { - failure = ex; - } else { - failure.addSuppressed(ex); + public synchronized void remove(final Resource resource) throws DataStoreException { + if (resource instanceof DataStore) try { + if (resource instanceof Store) { + final Path path = ((Store) resource).location; + if (Files.isSameFile(path.getParent(), location)) { + ((Store) resource).close(); + deleteRecursively(path, true); + children.remove(path); + return; + } + } else if (resource instanceof FileSystemResource) { + final Path[] componentPaths = ((FileSystemResource) resource).getResourcePaths().clone(); + for (final Path root : componentPaths) { + if (Files.isSameFile(root.getParent(), location)) { + for (final Path path : componentPaths) { + if (path.startsWith(root)) { + Files.delete(path); + } + } + children.values().removeIf((e) -> e == resource); + components = null; // Clear cache. TODO: we should do something more efficient. + return; } } } - if (failure != null) { - throw failure; - } + } catch (IOException e) { + throw new DataStoreException(Resources.format(Resources.Keys.CanNotRemoveResource_2, + getDisplayName(), ((DataStore) resource).getDisplayName()), e); } + throw new DataStoreException(Resources.format(Resources.Keys.NoSuchResourceInAggregate_2, + getDisplayName(), StoreUtilities.getLabel(resource))); } /** - * Writable version of the store which rely on given datastore provider to create new types. + * Deletes all files and sub-directories in the specified directory. + * This method does nothing if the given {@code root} is a file rather than a directory. + * The root directory is left in place (after being emptied) if {@code deleteRoot} is {@code false}. * - * Note 1 : this implementation is experimental. - * Note 2 : it has not been tested since we do not have writable feature sets yet. + * @param root the directory to delete with all sub-directories. + * @param deleteRoot {@code true} for deleting also {@code root}, or {@code false} for leaving it empty. */ - 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); - } + static void deleteRecursively(final Path root, final boolean deleteRoot) throws IOException { + Files.walkFileTree(root, new SimpleFileVisitor<Path>() { + /** + * Count of the number of time we entered in a directory. + * We use this count for detecting when a file is the {@code root} argument. + */ + private int depth; - /** - * 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."); + /** + * Invoked for a directory before entries in the directory are visited. + */ + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + depth++; + return FileVisitResult.CONTINUE; } - //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"); + /** + * Invoked for a file in a directory. Deletes the file provided that it is not the {@code root} argument. + * The later can happen if the path given to {@code deleteRecursively(…)} is a file rather than a directory. + */ + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (depth > 0) Files.delete(file); + return FileVisitResult.CONTINUE; } - 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); - } - } + /** + * Invoked for a directory after entries in the directory have been visited. + * This method delete the directory unless it is the root and {@code deleteRoot} is {@code false}. + */ + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException ex) throws IOException { + if (ex != null) throw ex; + if (--depth > 0 || deleteRoot) { + Files.delete(dir); } + return FileVisitResult.CONTINUE; } - } + }); } }
Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java?rev=1825102&r1=1825101&r2=1825102&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java [UTF-8] (original) +++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java [UTF-8] Thu Feb 22 22:18:05 2018 @@ -57,7 +57,7 @@ import org.apache.sis.util.Version; * * @author Martin Desruisseaux (Geomatys) * @author Johann Sorel (Geomatys) - * @version 0.8 + * @version 1.0 * @since 0.3 * @module */ @@ -69,16 +69,35 @@ public abstract class DataStoreProvider * * <p>Implementors are encouraged to define a parameter with this name * to ensure a common and consistent definition among providers. - * The parameter should be defined as mandatory and declared with a well-known Java class such as + * The parameter should be defined as mandatory and typed with a well-known Java class such as * {@link java.net.URI}, {@link java.nio.file.Path}, JDBC {@linkplain javax.sql.DataSource}, <i>etc</i>. * The type should have a compact textual representation, for serialization in XML or configuration files. * Consequently {@link java.io.InputStream} and {@link java.nio.channels.Channel} should be avoided.</p> * + * @see #CREATE * @see #getOpenParameters() */ public static final String LOCATION = "location"; /** + * Name of the parameter that specifies whether to allow creation of a new {@code DataStore} if none exist + * at the given location. A parameter named {@value} may be included in the group of parameters returned by + * {@link #getOpenParameters()} if the data store supports write operations. The parameter value is often a + * {@link Boolean}, but other types are allowed. The default value should be {@link Boolean#FALSE} or equivalent. + * + * <p>Implementors are encouraged to define a parameter with this name in complement to the {@value #LOCATION} + * parameter if write operations are supported. The parameter should be defined as optional and typed with a + * well-known Java class such as {@link Boolean} or {@link String}. If this parameter value is not set or is + * set to {@code false}, then the {@link #open(ParameterValueGroup)} method should fail if no file or database + * exists at the URL or path given by the {@value #LOCATION} parameter. Otherwise if this parameter is set to + * {@code true}, then the {@code open(…)} method may create files, a directory or a database at the given location.</p> + * + * @see #LOCATION + * @see #getOpenParameters() + */ + public static final String CREATE = "create"; + + /** * Creates a new provider. */ protected DataStoreProvider() { @@ -156,7 +175,8 @@ public abstract class DataStoreProvider * from a path or URL, together with additional information like character encoding. * * <p>Implementors are responsible for declaring all parameters and whether they are mandatory or optional. - * It is recommended to define at least a parameter named {@value #LOCATION}. + * It is recommended to define at least a parameter named {@value #LOCATION}, completed by {@value #CREATE} + * if the data store supports write operations. * That parameter will be recognized by the default {@code DataStoreProvider} methods and used whenever a * {@link StorageConnector} is required.</p> * @@ -174,6 +194,8 @@ public abstract class DataStoreProvider * * @return description of the parameters required or accepted for opening a {@link DataStore}. * + * @see #LOCATION + * @see #CREATE * @see #open(ParameterValueGroup) * @see DataStore#getOpenParameters() * @@ -280,6 +302,8 @@ public abstract class DataStoreProvider * @return a data store implementation associated with this provider for the given parameters. * @throws DataStoreException if an error occurred while creating the data store instance. * + * @see #LOCATION + * @see #CREATE * @see #getOpenParameters() * * @since 0.8 Modified: sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java?rev=1825102&r1=1825101&r2=1825102&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java [UTF-8] (original) +++ sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java [UTF-8] Thu Feb 22 22:18:05 2018 @@ -91,7 +91,7 @@ public final strictfp class StoreTest ex final Set<String> identifiers = new HashSet<>(Arrays.asList("Sample 1", "Sample 2", "Sample 3", "data4")); final Parameters params = Parameters.castOrWrap(FolderStoreProvider.PARAMETERS.createValue()); params.parameter("location").setValue(testDirectory()); - params.parameter("provider").setValue("XML"); + params.parameter("format").setValue("XML"); try (Store store = (Store) FolderStoreProvider.INSTANCE.open(params)) { assertEquals("Expected three data stores.", 3, store.components().size()); verifyContent(store, identifiers);
