/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2011, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.data;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.logging.Level;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.IllegalNameException;
import static org.geotoolkit.data.AbstractFileFeatureStoreFactory.*;
import static org.geotoolkit.data.AbstractFolderFeatureStoreFactory.*;
import org.geotoolkit.data.internal.GenericNameIndex;
import org.geotoolkit.data.query.DefaultQueryCapabilities;
import org.geotoolkit.data.query.Query;
import org.geotoolkit.data.query.QueryCapabilities;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.parameter.Parameters;
import org.geotoolkit.storage.DataFileStore;
import org.geotoolkit.version.VersionControl;
import org.geotoolkit.version.VersioningException;
import org.opengis.util.GenericName;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.identity.FeatureId;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
/**
* Handle a folder of single file FeatureStore.
*
* @author Johann Sorel (Geomatys)
* @author Cédric Briançon (Geomatys)
* @module
*/
public class DefaultFolderFeatureStore extends AbstractFeatureStore implements DataFileStore {
/**
* Listen to changes in sub stores and propagate them.
*/
private final FeatureStoreListener subListener = new FeatureStoreListener() {
@Override
public void structureChanged(FeatureStoreManagementEvent event) {
event = event.copy(DefaultFolderFeatureStore.this);
sendStructureEvent(event);
}
@Override
public void contentChanged(FeatureStoreContentEvent event) {
event = event.copy(DefaultFolderFeatureStore.this);
sendContentEvent(event);
}
};
private final ParameterValueGroup folderParameters;
private final AbstractFolderFeatureStoreFactory folderFactory;
private final FileFeatureStoreFactory singleFileFactory;
private final ParameterValueGroup singleFileDefaultParameters;
private GenericNameIndex<FeatureStore> stores = null;
public DefaultFolderFeatureStore(final ParameterValueGroup params, final AbstractFolderFeatureStoreFactory factory){
super(params);
this.folderParameters = params;
this.folderFactory = factory;
this.singleFileFactory = this.folderFactory.getSingleFileFactory();
final ParameterDescriptorGroup desc = singleFileFactory.getParametersDescriptor();
singleFileDefaultParameters = desc.createValue();
for(GeneralParameterDescriptor pdesc : desc.descriptors()){
if(pdesc == PATH || pdesc.getName().getCode().equals(IDENTIFIER.getName().getCode())) {
continue;
}
Parameters.getOrCreate((ParameterDescriptor)pdesc, singleFileDefaultParameters)
.setValue(folderParameters.parameter(pdesc.getName().getCode()).getValue());
}
}
/**
* {@inheritDoc}
*/
@Override
public FeatureStoreFactory getFactory() {
return folderFactory;
}
/**
* Fallthrought to sub feature stores.
*/
@Override
public VersionControl getVersioning(String typeName) throws VersioningException {
try {
typeCheck(typeName);
} catch (DataStoreException ex) {
throw new VersioningException(ex);
}
final FeatureStore store;
try {
store = stores.get(typeName);
} catch (IllegalNameException ex) {
throw new VersioningException(ex);
}
return store.getVersioning(typeName);
}
/**
* {@inheritDoc}
*/
@Override
public synchronized Set<GenericName> getNames() throws DataStoreException {
if(stores == null){
this.stores = new GenericNameIndex<>();
final Path folder = getFolder(folderParameters);
if(!Files.exists(folder)){
try{
Files.createDirectory(folder);
}catch(IOException | SecurityException ex){
throw new DataStoreException(ex.getMessage(), ex);
}
}
Boolean recursive = Parameters.value(RECURSIVE, folderParameters);
if (recursive == null) {
recursive = RECURSIVE.getDefaultValue();
}
try {
if (recursive) {
Files.walkFileTree(folder, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
testFile(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (Files.isHidden(dir) || Files.isSymbolicLink(dir)) {
return FileVisitResult.SKIP_SUBTREE;
}
return FileVisitResult.CONTINUE;
}
});
} else {
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(folder)) {
for (Path path : directoryStream) {
testFile(path);
}
}
}
} catch (IOException ex) {
getLogger().log(Level.WARNING, ex.getLocalizedMessage(), ex);
}
}
return stores.getNames();
}
private void testFile(Path file) throws IOException {
if (Files.isHidden(file) || Files.isSymbolicLink(file)) {
return;//skip hidden and sym link files
}
final ParameterValueGroup params = singleFileDefaultParameters.clone();
Parameters.getOrCreate(PATH, params).setValue(file.toUri());
if (singleFileFactory.canProcess(params)) {
try {
final FeatureStore fileDS = (FeatureStore) singleFileFactory.open(params);
fileDS.addStorageListener(subListener);
stores.add(fileDS.getNames().iterator().next(), fileDS);
} catch (DataStoreException ex) {
getLogger().log(Level.WARNING, ex.getLocalizedMessage(), ex);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void createFeatureType(final FeatureType featureType) throws DataStoreException {
final GenericName typeName = featureType.getName();
if(getNames().contains(typeName)){
throw new DataStoreException("Type name "+ typeName + " already exists.");
}
final ParameterValueGroup params = singleFileDefaultParameters.clone();
try {
final Path folder = getFolder(folderParameters);
final String fileName = typeName.tip().toString() + singleFileFactory.getFileExtensions()[0];
final Path newFile = folder.resolve(fileName);
Parameters.getOrCreate(PATH, params).setValue(newFile.toUri().toURL());
} catch (MalformedURLException ex) {
throw new DataStoreException(ex);
}
final FeatureStore store = (FeatureStore) singleFileFactory.create(params);
store.addStorageListener(subListener);
store.createFeatureType(featureType);
stores.add(typeName, store);
}
/**
* Unsupported, throws a {@link DataStoreException}.
*/
@Override
public void updateFeatureType(final FeatureType featureType) throws DataStoreException {
throw new DataStoreException("Not supported.");
}
/**
* Unsupported, throws a {@link DataStoreException}.
*/
@Override
public synchronized void deleteFeatureType(final String typeName) throws DataStoreException {
final FeatureStore store = stores.get(typeName);
if (store == null) {
throw new DataStoreException("There's no data with the following type name : "+typeName);
}
// We should get a file feature store.
final Path[] sourceFiles;
try {
if (store instanceof DataFileStore) {
sourceFiles = ((DataFileStore) store).getDataFiles();
} else {
// Not a file store ? We try to find an url parameter and see if it's a file one.
final URI fileURI = Parameters.value(PATH, store.getConfiguration());
if (fileURI == null) {
throw new DataStoreException("Source data cannot be reached for type name : " + typeName);
}
sourceFiles = new Path[]{Paths.get(fileURI)};
}
for (Path path : sourceFiles) {
Files.deleteIfExists(path);
}
} catch (IOException e) {
throw new DataStoreException("Source data cannot be deleted for type name : " + typeName, e);
}
stores.remove(store.getNames().iterator().next());
}
/**
* {@inheritDoc}
*/
@Override
public FeatureType getFeatureType(final String typeName) throws DataStoreException {
typeCheck(typeName);
final FeatureStore store = stores.get(typeName);
return store.getFeatureType(typeName);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isWritable(final String typeName) throws DataStoreException {
typeCheck(typeName);
final FeatureStore store = stores.get(typeName);
return store.isWritable(typeName);
}
/**
* Unsupported, throws a {@link DataStoreException}.
*/
@Override
public QueryCapabilities getQueryCapabilities() {
return new DefaultQueryCapabilities(false, false);
}
/**
* {@inheritDoc}
*/
@Override
public List<FeatureId> addFeatures(final String groupName, final Collection<? extends Feature> newFeatures,
final Hints hints) throws DataStoreException {
typeCheck(groupName);
final FeatureStore store = stores.get(groupName);
return store.addFeatures(groupName, newFeatures);
}
/**
* {@inheritDoc}
*/
@Override
public void updateFeatures(final String groupName, final Filter filter,
final Map<String, ?> values) throws DataStoreException {
typeCheck(groupName);
final FeatureStore store = stores.get(groupName);
store.updateFeatures(groupName, filter, values);
}
/**
* {@inheritDoc}
*/
@Override
public void removeFeatures(final String groupName, final Filter filter) throws DataStoreException {
typeCheck(groupName);
final FeatureStore store = stores.get(groupName);
store.removeFeatures(groupName, filter);
}
/**
* {@inheritDoc}
*/
@Override
public FeatureReader getFeatureReader(final Query query) throws DataStoreException {
final String name = query.getTypeName();
typeCheck(name);
final FeatureStore store = stores.get(name);
return store.getFeatureReader(query);
}
/**
* {@inheritDoc}
*/
@Override
public FeatureWriter getFeatureWriter(Query query) throws DataStoreException {
final String typeName = query.getTypeName();
typeCheck(typeName);
final FeatureStore store = stores.get(typeName);
return store.getFeatureWriter(query);
}
private Path getFolder(final ParameterValueGroup params) throws DataStoreException{
final URI uri = Parameters.value(FOLDER_PATH, params);
try {
return Paths.get(uri);
} catch (FileSystemNotFoundException | IllegalArgumentException e) {
throw new DataStoreException(e);
}
}
/**
* {@inheritDoc}
*/
public Path[] getDataFiles() throws DataStoreException {
final Path folder = getFolder(folderParameters);
return new Path[]{ folder };
}
@Override
public void refreshMetaModel() {
stores=null;
}
}