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 */
065        EXTERNAL_GPX_SCRIPT("https?://.*exportgpx.*"),
066        /** External GPX file */
067        EXTERNAL_GPX_FILE  ("https?://.*/(.*\\.gpx)");
068
069        private final String urlPattern;
070
071        GpxUrlPattern(String urlPattern) {
072            this.urlPattern = urlPattern;
073        }
074
075        /**
076         * Returns the URL pattern.
077         * @return the URL pattern
078         */
079        public String pattern() {
080            return urlPattern;
081        }
082    }
083
084    /**
085     * Patterns for Note download URLs.
086     * @since 12679
087     */
088    public enum NoteUrlPattern {
089        /** URL of OSM API Notes endpoint */
090        API_URL  ("https?://.*/api/0.6/notes.*"),
091        /** URL of OSM API Notes compressed dump file */
092        DUMP_FILE("https?://.*/(.*\\.osn(\\.(gz|xz|bz2?|zip))?)");
093
094        private final String urlPattern;
095
096        NoteUrlPattern(String urlPattern) {
097            this.urlPattern = urlPattern;
098        }
099
100        /**
101         * Returns the URL pattern.
102         * @return the URL pattern
103         */
104        public String pattern() {
105            return urlPattern;
106        }
107    }
108
109    // CHECKSTYLE.ON: SingleSpaceSeparator
110    // CHECKSTYLE.ON: MethodParamPad
111
112    protected final String url;
113
114    /**
115     * Constructs a new {@code OsmServerLocationReader}.
116     * @param url The URL to fetch
117     */
118    public OsmServerLocationReader(String url) {
119        this.url = url;
120    }
121
122    /**
123     * Returns the URL to fetch
124     * @return the URL to fetch
125     * @since 15247
126     */
127    public final String getUrl() {
128        return url;
129    }
130
131    protected abstract static class Parser<T> {
132        protected final ProgressMonitor progressMonitor;
133        protected final Compression compression;
134        protected InputStream in;
135
136        public Parser(ProgressMonitor progressMonitor, Compression compression) {
137            this.progressMonitor = progressMonitor;
138            this.compression = compression;
139        }
140
141        public abstract T parse() throws OsmTransferException, IllegalDataException, IOException, SAXException;
142    }
143
144    protected final <T> T doParse(Parser<T> parser, final ProgressMonitor progressMonitor) throws OsmTransferException {
145        progressMonitor.beginTask(tr("Contacting Server...", 10));
146        try {
147            return parser.parse();
148        } catch (OsmTransferException e) {
149            throw e;
150        } catch (IOException | SAXException | IllegalDataException e) {
151            if (cancel)
152                return null;
153            throw new OsmTransferException(e);
154        } finally {
155            progressMonitor.finishTask();
156            activeConnection = null;
157            Utils.close(parser.in);
158            parser.in = null;
159        }
160    }
161
162    @Override
163    public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException {
164        return parseOsm(progressMonitor, Compression.NONE);
165    }
166
167    @Override
168    public DataSet parseOsm(ProgressMonitor progressMonitor, Compression compression) throws OsmTransferException {
169        return doParse(new OsmParser(progressMonitor, compression), progressMonitor);
170    }
171
172    @Override
173    public DataSet parseOsmChange(ProgressMonitor progressMonitor) throws OsmTransferException {
174        return parseOsmChange(progressMonitor, Compression.NONE);
175    }
176
177    @Override
178    public DataSet parseOsmChange(ProgressMonitor progressMonitor, Compression compression) throws OsmTransferException {
179        return doParse(new OsmChangeParser(progressMonitor, compression), progressMonitor);
180    }
181
182    @Override
183    public GpxData parseRawGps(ProgressMonitor progressMonitor) throws OsmTransferException {
184        return parseRawGps(progressMonitor, Compression.NONE);
185    }
186
187    @Override
188    public GpxData parseRawGps(ProgressMonitor progressMonitor, Compression compression) throws OsmTransferException {
189        return doParse(new GpxParser(progressMonitor, compression), progressMonitor);
190    }
191
192    @Override
193    public List<Note> parseRawNotes(ProgressMonitor progressMonitor) throws OsmTransferException {
194        return parseRawNotes(progressMonitor, Compression.NONE);
195    }
196
197    @Override
198    public List<Note> parseRawNotes(ProgressMonitor progressMonitor, Compression compression) throws OsmTransferException {
199        return doParse(new NoteParser(progressMonitor, compression), progressMonitor);
200    }
201
202    protected class OsmParser extends Parser<DataSet> {
203        protected OsmParser(ProgressMonitor progressMonitor, Compression compression) {
204            super(progressMonitor, compression);
205        }
206
207        @Override
208        public DataSet parse() throws OsmTransferException, IllegalDataException, IOException {
209            in = getInputStreamRaw(url, progressMonitor.createSubTaskMonitor(9, false));
210            if (in == null)
211                return null;
212            progressMonitor.subTask(tr("Downloading OSM data..."));
213            InputStream uncompressedInputStream = compression.getUncompressedInputStream(in);
214            ProgressMonitor subTaskMonitor = progressMonitor.createSubTaskMonitor(1, false);
215            if ("application/json".equals(contentType)) {
216                return OsmJsonReader.parseDataSet(uncompressedInputStream, subTaskMonitor);
217            } else {
218                return OsmReader.parseDataSet(uncompressedInputStream, subTaskMonitor);
219            }
220        }
221    }
222
223    protected class OsmChangeParser extends Parser<DataSet> {
224        protected OsmChangeParser(ProgressMonitor progressMonitor, Compression compression) {
225            super(progressMonitor, compression);
226        }
227
228        @Override
229        public DataSet parse() throws OsmTransferException, IllegalDataException, IOException {
230            in = getInputStreamRaw(url, progressMonitor.createSubTaskMonitor(9, false));
231            if (in == null)
232                return null;
233            progressMonitor.subTask(tr("Downloading OSM data..."));
234            return OsmChangeReader.parseDataSet(compression.getUncompressedInputStream(in), progressMonitor.createSubTaskMonitor(1, false));
235        }
236    }
237
238    protected class GpxParser extends Parser<GpxData> {
239        protected GpxParser(ProgressMonitor progressMonitor, Compression compression) {
240            super(progressMonitor, compression);
241        }
242
243        @Override
244        public GpxData parse() throws OsmTransferException, IllegalDataException, IOException, SAXException {
245            in = getInputStreamRaw(url, progressMonitor.createSubTaskMonitor(1, true), null, true);
246            if (in == null)
247                return null;
248            progressMonitor.subTask(tr("Downloading OSM data..."));
249            GpxReader reader = new GpxReader(compression.getUncompressedInputStream(in));
250            gpxParsedProperly = reader.parse(false);
251            GpxData result = reader.getGpxData();
252            result.fromServer = isGpxFromServer(url);
253            return result;
254        }
255    }
256
257    protected class NoteParser extends Parser<List<Note>> {
258
259        public NoteParser(ProgressMonitor progressMonitor, Compression compression) {
260            super(progressMonitor, compression);
261        }
262
263        @Override
264        public List<Note> parse() throws OsmTransferException, IllegalDataException, IOException, SAXException {
265            in = getInputStream(url, progressMonitor.createSubTaskMonitor(1, true));
266            if (in == null) {
267                return Collections.emptyList();
268            }
269            progressMonitor.subTask(tr("Downloading OSM notes..."));
270            NoteReader reader = new NoteReader(compression.getUncompressedInputStream(in));
271            return reader.parse();
272        }
273    }
274
275    /**
276     * Determines if the given URL denotes an OSM gpx-related API call.
277     * @param url The url to check
278     * @return true if the url matches "Trace ID" API call or "Trackpoints bbox" API call, false otherwise
279     * @see GpxData#fromServer
280     * @since 12679
281     */
282    public static final boolean isGpxFromServer(String url) {
283        return url != null && (url.matches(GpxUrlPattern.TRACE_ID.pattern()) || url.matches(GpxUrlPattern.TRACKPOINTS_BBOX.pattern()));
284    }
285}