001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.io.IOException;
007import java.io.InputStream;
008import java.util.Collections;
009import java.util.List;
010
011import org.openstreetmap.josm.data.gpx.GpxData;
012import org.openstreetmap.josm.data.notes.Note;
013import org.openstreetmap.josm.data.osm.DataSet;
014import org.openstreetmap.josm.gui.progress.ProgressMonitor;
015import org.openstreetmap.josm.tools.Utils;
016import org.xml.sax.SAXException;
017
018/**
019 * Read content from OSM server for a given URL
020 * @since 1146
021 */
022public class OsmServerLocationReader extends OsmServerReader {
023
024    // CHECKSTYLE.OFF: MethodParamPad
025    // CHECKSTYLE.OFF: SingleSpaceSeparator
026
027    /**
028     * Patterns for OSM data download URLs.
029     * @since 12679
030     */
031    public enum OsmUrlPattern {
032        OSM_API_URL           ("https?://.*/api/0.6/(map|nodes?|ways?|relations?|\\*).*"),
033        OVERPASS_API_URL      ("https?://.*/interpreter\\?data=.*"),
034        OVERPASS_API_XAPI_URL ("https?://.*/xapi(\\?.*\\[@meta\\]|_meta\\?).*"),
035        EXTERNAL_OSM_FILE     ("https?://.*/.*\\.osm");
036
037        private final String urlPattern;
038
039        OsmUrlPattern(String urlPattern) {
040            this.urlPattern = urlPattern;
041        }
042
043        /**
044         * Returns the URL pattern.
045         * @return the URL pattern
046         */
047        public String pattern() {
048            return urlPattern;
049        }
050    }
051
052    /**
053     * Patterns for GPX download URLs.
054     * @since 12679
055     */
056    public enum GpxUrlPattern {
057        TRACE_ID     ("https?://.*(osm|openstreetmap).org/trace/\\p{Digit}+/data"),
058        USER_TRACE_ID("https?://.*(osm|openstreetmap).org/user/[^/]+/traces/(\\p{Digit}+)"),
059        EDIT_TRACE_ID("https?://.*(osm|openstreetmap).org/edit/?\\?gpx=(\\p{Digit}+)(#.*)?"),
060
061        TRACKPOINTS_BBOX("https?://.*/api/0.6/trackpoints\\?bbox=.*,.*,.*,.*"),
062        TASKING_MANAGER("https?://.*/api/v\\p{Digit}+/project/\\p{Digit}+/tasks_as_gpx?.*"),
063
064        EXTERNAL_GPX_SCRIPT("https?://.*exportgpx.*"),
065        EXTERNAL_GPX_FILE  ("https?://.*/(.*\\.gpx)");
066
067        private final String urlPattern;
068
069        GpxUrlPattern(String urlPattern) {
070            this.urlPattern = urlPattern;
071        }
072
073        /**
074         * Returns the URL pattern.
075         * @return the URL pattern
076         */
077        public String pattern() {
078            return urlPattern;
079        }
080    }
081
082    /**
083     * Patterns for Note download URLs.
084     * @since 12679
085     */
086    public enum NoteUrlPattern {
087        /** URL of OSM API Notes endpoint */
088        API_URL  ("https?://.*/api/0.6/notes.*"),
089        /** URL of OSM API Notes compressed dump file */
090        DUMP_FILE("https?://.*/(.*\\.osn(\\.(gz|xz|bz2?|zip))?)");
091
092        private final String urlPattern;
093
094        NoteUrlPattern(String urlPattern) {
095            this.urlPattern = urlPattern;
096        }
097
098        /**
099         * Returns the URL pattern.
100         * @return the URL pattern
101         */
102        public String pattern() {
103            return urlPattern;
104        }
105    }
106
107    // CHECKSTYLE.ON: SingleSpaceSeparator
108    // CHECKSTYLE.ON: MethodParamPad
109
110    protected final String url;
111
112    /**
113     * Constructs a new {@code OsmServerLocationReader}.
114     * @param url The URL to fetch
115     */
116    public OsmServerLocationReader(String url) {
117        this.url = url;
118    }
119
120    protected abstract static class Parser<T> {
121        protected final ProgressMonitor progressMonitor;
122        protected final Compression compression;
123        protected InputStream in;
124
125        public Parser(ProgressMonitor progressMonitor, Compression compression) {
126            this.progressMonitor = progressMonitor;
127            this.compression = compression;
128        }
129
130        public abstract T parse() throws OsmTransferException, IllegalDataException, IOException, SAXException;
131    }
132
133    protected final <T> T doParse(Parser<T> parser, final ProgressMonitor progressMonitor) throws OsmTransferException {
134        progressMonitor.beginTask(tr("Contacting Server...", 10));
135        try {
136            return parser.parse();
137        } catch (OsmTransferException e) {
138            throw e;
139        } catch (IOException | SAXException | IllegalDataException e) {
140            if (cancel)
141                return null;
142            throw new OsmTransferException(e);
143        } finally {
144            progressMonitor.finishTask();
145            activeConnection = null;
146            Utils.close(parser.in);
147            parser.in = null;
148        }
149    }
150
151    @Override
152    public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException {
153        return parseOsm(progressMonitor, Compression.NONE);
154    }
155
156    @Override
157    public DataSet parseOsm(ProgressMonitor progressMonitor, Compression compression) throws OsmTransferException {
158        return doParse(new OsmParser(progressMonitor, compression), progressMonitor);
159    }
160
161    @Override
162    public DataSet parseOsmChange(ProgressMonitor progressMonitor) throws OsmTransferException {
163        return parseOsmChange(progressMonitor, Compression.NONE);
164    }
165
166    @Override
167    public DataSet parseOsmChange(ProgressMonitor progressMonitor, Compression compression) throws OsmTransferException {
168        return doParse(new OsmChangeParser(progressMonitor, compression), progressMonitor);
169    }
170
171    @Override
172    public GpxData parseRawGps(ProgressMonitor progressMonitor) throws OsmTransferException {
173        return parseRawGps(progressMonitor, Compression.NONE);
174    }
175
176    @Override
177    public GpxData parseRawGps(ProgressMonitor progressMonitor, Compression compression) throws OsmTransferException {
178        return doParse(new GpxParser(progressMonitor, compression), progressMonitor);
179    }
180
181    @Override
182    public List<Note> parseRawNotes(ProgressMonitor progressMonitor) throws OsmTransferException {
183        return parseRawNotes(progressMonitor, Compression.NONE);
184    }
185
186    @Override
187    public List<Note> parseRawNotes(ProgressMonitor progressMonitor, Compression compression) throws OsmTransferException {
188        return doParse(new NoteParser(progressMonitor, compression), progressMonitor);
189    }
190
191    protected class OsmParser extends Parser<DataSet> {
192        protected OsmParser(ProgressMonitor progressMonitor, Compression compression) {
193            super(progressMonitor, compression);
194        }
195
196        @Override
197        public DataSet parse() throws OsmTransferException, IllegalDataException, IOException {
198            in = getInputStreamRaw(url, progressMonitor.createSubTaskMonitor(9, false));
199            if (in == null)
200                return null;
201            progressMonitor.subTask(tr("Downloading OSM data..."));
202            return OsmReader.parseDataSet(compression.getUncompressedInputStream(in), progressMonitor.createSubTaskMonitor(1, false));
203        }
204    }
205
206    protected class OsmChangeParser extends Parser<DataSet> {
207        protected OsmChangeParser(ProgressMonitor progressMonitor, Compression compression) {
208            super(progressMonitor, compression);
209        }
210
211        @Override
212        public DataSet parse() throws OsmTransferException, IllegalDataException, IOException {
213            in = getInputStreamRaw(url, progressMonitor.createSubTaskMonitor(9, false));
214            if (in == null)
215                return null;
216            progressMonitor.subTask(tr("Downloading OSM data..."));
217            return OsmChangeReader.parseDataSet(compression.getUncompressedInputStream(in), progressMonitor.createSubTaskMonitor(1, false));
218        }
219    }
220
221    protected class GpxParser extends Parser<GpxData> {
222        protected GpxParser(ProgressMonitor progressMonitor, Compression compression) {
223            super(progressMonitor, compression);
224        }
225
226        @Override
227        public GpxData parse() throws OsmTransferException, IllegalDataException, IOException, SAXException {
228            in = getInputStreamRaw(url, progressMonitor.createSubTaskMonitor(1, true), null, true);
229            if (in == null)
230                return null;
231            progressMonitor.subTask(tr("Downloading OSM data..."));
232            GpxReader reader = new GpxReader(compression.getUncompressedInputStream(in));
233            gpxParsedProperly = reader.parse(false);
234            GpxData result = reader.getGpxData();
235            result.fromServer = isGpxFromServer(url);
236            return result;
237        }
238    }
239
240    protected class NoteParser extends Parser<List<Note>> {
241
242        public NoteParser(ProgressMonitor progressMonitor, Compression compression) {
243            super(progressMonitor, compression);
244        }
245
246        @Override
247        public List<Note> parse() throws OsmTransferException, IllegalDataException, IOException, SAXException {
248            in = getInputStream(url, progressMonitor.createSubTaskMonitor(1, true));
249            if (in == null) {
250                return Collections.emptyList();
251            }
252            progressMonitor.subTask(tr("Downloading OSM notes..."));
253            NoteReader reader = new NoteReader(compression.getUncompressedInputStream(in));
254            return reader.parse();
255        }
256    }
257
258    /**
259     * Determines if the given URL denotes an OSM gpx-related API call.
260     * @param url The url to check
261     * @return true if the url matches "Trace ID" API call or "Trackpoints bbox" API call, false otherwise
262     * @see GpxData#fromServer
263     * @since 12679
264     */
265    public static final boolean isGpxFromServer(String url) {
266        return url != null && (url.matches(GpxUrlPattern.TRACE_ID.pattern()) || url.matches(GpxUrlPattern.TRACKPOINTS_BBOX.pattern()));
267    }
268}