/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2010, 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.gpx; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.data.AbstractFeatureStore; import org.geotoolkit.data.FeatureReader; import org.geotoolkit.data.FeatureStoreFactory; import org.geotoolkit.data.FeatureStoreRuntimeException; import org.geotoolkit.data.FeatureWriter; 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.opengis.util.GenericName; import org.geotoolkit.nio.IOUtilities; import org.geotoolkit.parameter.Parameters; import org.geotoolkit.storage.DataFileStore; import org.opengis.filter.Filter; import org.opengis.filter.identity.FeatureId; import org.opengis.parameter.ParameterValueGroup; import javax.xml.stream.XMLStreamException; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.sis.internal.gpx.GPXStore; import org.apache.sis.internal.gpx.GPXReader; import org.apache.sis.internal.gpx.GPXWriter100; import org.apache.sis.internal.gpx.GPXWriter110; import org.apache.sis.internal.gpx.GPXConstants; import org.apache.sis.internal.gpx.Metadata; import org.apache.sis.storage.StorageConnector; import org.apache.sis.util.Version; import org.geotoolkit.data.internal.GenericNameIndex; import org.geotoolkit.storage.DataStores; import org.opengis.feature.Feature; import org.opengis.feature.FeatureType; /** * GPX DataStore, holds 4 feature types. * - One global which match the reading order in the file * - One WayPoint * - One Routes * - One Tracks * * @author Johann Sorel (Geomatys) * @module */ public class GPXFeatureStore extends AbstractFeatureStore implements DataFileStore { private final ReadWriteLock RWLock = new ReentrantReadWriteLock(); private final ReadWriteLock TempLock = new ReentrantReadWriteLock(); private static final QueryCapabilities QUERY_CAPABILITIES = new DefaultQueryCapabilities(false); private final GenericNameIndex<FeatureType> index = new GenericNameIndex<>(); private final Path file; /** * @deprecated use {@link #GPXFeatureStore(Path)} instead */ @Deprecated public GPXFeatureStore(final File f) throws MalformedURLException, DataStoreException{ this(f.toPath()); } public GPXFeatureStore(final Path f) throws MalformedURLException, DataStoreException{ this(toParameter(f)); } public GPXFeatureStore(final ParameterValueGroup params) throws DataStoreException{ super(params); final URI uri = (URI) params.parameter(GPXFeatureStoreFactory.PATH.getName().toString()).getValue(); try { this.file = IOUtilities.toPath(uri); } catch (IOException ex) { throw new DataStoreException(ex); } index.add(GPXConstants.TYPE_GPX_ENTITY.getName(), GPXConstants.TYPE_GPX_ENTITY); index.add(GPXConstants.TYPE_WAYPOINT.getName(), GPXConstants.TYPE_WAYPOINT); index.add(GPXConstants.TYPE_ROUTE.getName(), GPXConstants.TYPE_ROUTE); index.add(GPXConstants.TYPE_TRACK.getName(), GPXConstants.TYPE_TRACK); } private static ParameterValueGroup toParameter(final Path f) throws MalformedURLException{ final ParameterValueGroup params = GPXFeatureStoreFactory.PARAMETERS_DESCRIPTOR.createValue(); Parameters.getOrCreate(GPXFeatureStoreFactory.PATH, params).setValue(f.toUri()); return params; } @Override public FeatureStoreFactory getFactory() { return (FeatureStoreFactory) DataStores.getFactoryById(GPXFeatureStoreFactory.NAME); } private GPXReader createReader() throws Exception { // Too many exceptions for listing them all. final StorageConnector c = new StorageConnector(file); return new GPXReader(new GPXStore(c), c); } public Metadata getGPXMetaData() throws DataStoreException{ if(Files.exists(file)){ try { RWLock.readLock().lock(); final Metadata data; try (GPXReader reader = createReader()) { data = reader.getMetadata(); } return data; } catch (Exception ex) { throw new DataStoreException(ex); } finally{ RWLock.readLock().unlock(); } }else{ return null; } } private Path createWriteFile() throws MalformedURLException{ return IOUtilities.changeExtension(file, "wgpx"); } @Override public Set<GenericName> getNames() throws DataStoreException { return index.getNames(); } @Override public FeatureType getFeatureType(final String typeName) throws DataStoreException { return index.get(typeName); } @Override public boolean isWritable(String typeName) throws DataStoreException { typeCheck(typeName); return Files.isWritable(file) && getFeatureType(typeName) != GPXConstants.TYPE_GPX_ENTITY; } @Override public FeatureReader getFeatureReader(final Query query) throws DataStoreException { final FeatureType ft = getFeatureType(query.getTypeName()); final FeatureReader fr = new GPXFeatureReader(ft); return handleRemaining(fr, query); } @Override public FeatureWriter getFeatureWriter(Query query) throws DataStoreException { final FeatureType ft = getFeatureType(query.getTypeName()); final FeatureWriter fw = new GPXFeatureWriter(ft); return handleRemaining(fw, query.getFilter()); } //////////////////////////////////////////////////////////////////////////// // FALLTHROUGHT OR NOT IMPLEMENTED ///////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// @Override public QueryCapabilities getQueryCapabilities() { return QUERY_CAPABILITIES; } @Override public void createFeatureType(final FeatureType featureType) throws DataStoreException { throw new DataStoreException("New schema creation not allowed on GPX files."); } @Override public void deleteFeatureType(final String typeName) throws DataStoreException { throw new DataStoreException("Delete schema not allowed on GPX files."); } @Override public void updateFeatureType(final FeatureType featureType) throws DataStoreException { throw new DataStoreException("Update schema not allowed on GPX files."); } @Override public List<FeatureId> addFeatures(final String groupName, final Collection<? extends Feature> newFeatures, final Hints hints) throws DataStoreException { return handleAddWithFeatureWriter(groupName, newFeatures, hints); } @Override public void updateFeatures(final String groupName, final Filter filter, final Map<String, ? extends Object> values) throws DataStoreException { handleUpdateWithFeatureWriter(groupName, filter, values); } @Override public void removeFeatures(final String groupName, final Filter filter) throws DataStoreException { handleRemoveWithFeatureWriter(groupName, filter); } @Override public Path[] getDataFiles() throws DataStoreException { return new Path[] { this.file }; } private class GPXFeatureReader implements FeatureReader{ protected final FeatureType restriction; protected final GPXReader reader; protected Feature current = null; private GPXFeatureReader(final FeatureType restriction) throws DataStoreException{ RWLock.readLock().lock(); this.restriction = restriction; if(Files.exists(file)){ try { reader = createReader(); } catch (Exception ex) { throw new DataStoreException(ex); } }else{ reader = null; } } @Override public FeatureType getFeatureType() { return restriction; } @Override public Feature next() throws FeatureStoreRuntimeException { read(); final Feature ob = current; current = null; if(ob == null){ throw new FeatureStoreRuntimeException("No more records."); } return ob; } @Override public boolean hasNext() throws FeatureStoreRuntimeException { read(); return current != null; } private void read() throws FeatureStoreRuntimeException{ if(current != null) return; if(reader == null) return; try { while(reader.hasNext()) { current = reader.next(); if(restriction == GPXConstants.TYPE_GPX_ENTITY || current.getType() == restriction){ return; //type match } } } catch (XMLStreamException ex) { throw new FeatureStoreRuntimeException(ex); } current = null; } @Override public void close() { RWLock.readLock().unlock(); if(reader != null){ try { reader.close(); } catch (IOException | XMLStreamException ex) { throw new FeatureStoreRuntimeException(ex); } } } @Override public void remove() { throw new FeatureStoreRuntimeException("Not supported on reader."); } } private class GPXFeatureWriter extends GPXFeatureReader implements FeatureWriter{ private final FeatureType writeRestriction; private final GPXWriter100 writer; private final Path writeFile; private Feature next = null; private Feature edited = null; private Feature lastWritten = null; private GPXFeatureWriter(final FeatureType restriction) throws DataStoreException{ super(GPXConstants.TYPE_GPX_ENTITY); if (restriction == GPXConstants.TYPE_GPX_ENTITY) { super.close(); //release read lock throw new DataStoreException("Writer not allowed on GPX entity writer, choose a defined type."); } this.writeRestriction = restriction; TempLock.writeLock().lock(); try{ writeFile = createWriteFile(); if (!Files.exists(writeFile)) { Files.createFile(writeFile); } final Version version = reader.getVersion(); if (((Integer) version.getMajor()) != 1) { throw new DataStoreException("Unsupported version"); } final GPXStore store = new GPXStore(new StorageConnector(writeFile)); switch ((Integer) version.getMinor()) { case 0: writer = new GPXWriter100(store, "Geotoolkit.org", writeFile, "UTF-8"); break; case 1: writer = new GPXWriter110(store, "Geotoolkit.org", writeFile, "UTF-8"); break; default: throw new DataStoreException("Unsupported version"); } writer.writeStartDocument(); writer.writeGPXTag(); writer.write(reader.getMetadata()); }catch(IOException | XMLStreamException ex){ throw new DataStoreException(ex); } } @Override public boolean hasNext() throws FeatureStoreRuntimeException { findNext(); return next != null; } @Override public Feature next() throws FeatureStoreRuntimeException { write(); findNext(); if(next != null){ edited = next; next = null; return edited; }else{ //we reach append mode if (writeRestriction != GPXConstants.TYPE_GPX_ENTITY) { edited = writeRestriction.newInstance(); }else{ throw new FeatureStoreRuntimeException("Writer append not allowed " + "on GPX entity writer, choose a defined type."); } } return edited; } private void findNext() { if(next != null) return; while(next==null && super.hasNext()){ final Feature candidate = super.next(); if(candidate.getType() == writeRestriction){ next = candidate; }else{ //not the wished type, write it and continue //since all types are store in one file //we must ensure everything is copied write(candidate); } } } @Override public void write() throws FeatureStoreRuntimeException { if(edited == null || lastWritten == edited) return; lastWritten = edited; write(edited); } private void write(final Feature feature) throws FeatureStoreRuntimeException { final FeatureType ft = feature.getType(); try{ if (ft == GPXConstants.TYPE_WAYPOINT) { writer.writeWayPoint(feature, GPXConstants.TAG_WPT); } else if (ft == GPXConstants.TYPE_ROUTE) { writer.writeRoute(feature); } else if (ft == GPXConstants.TYPE_TRACK) { writer.writeTrack(feature); } else { throw new FeatureStoreRuntimeException("Writer not allowed on GPX " + "entity writer, choose a defined type." + ft.getName()); } }catch(XMLStreamException ex){ throw new FeatureStoreRuntimeException(ex); } } @Override public void close() { //write everything remaining if any while(hasNext()){ next(); } write(); try { writer.writeEndDocument(); writer.close(); } catch (IOException | XMLStreamException ex) { throw new FeatureStoreRuntimeException(ex); } //close read iterator super.close(); //flip files RWLock.writeLock().lock(); try{ Files.move(writeFile, file, StandardCopyOption.REPLACE_EXISTING); } catch (IOException ex) { throw new FeatureStoreRuntimeException(ex); } finally{ RWLock.writeLock().unlock(); } TempLock.writeLock().unlock(); } } @Override public void refreshMetaModel() { return; } }