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            InputStream uncompressedInputStream = compression.getUncompressedInputStream(in);
203            ProgressMonitor subTaskMonitor = progressMonitor.createSubTaskMonitor(1, false);
204            if ("application/json".equals(contentType)) {
205                return OsmJsonReader.parseDataSet(uncompressedInputStream, subTaskMonitor);
206            } else {
207                return OsmReader.parseDataSet(uncompressedInputStream, subTaskMonitor);
208            }
209        }
210    }
211
212    protected class OsmChangeParser extends Parser<DataSet> {
213        protected OsmChangeParser(ProgressMonitor progressMonitor, Compression compression) {
214            super(progressMonitor, compression);
215        }
216
217        @Override
218        public DataSet parse() throws OsmTransferException, IllegalDataException, IOException {
219            in = getInputStreamRaw(url, progressMonitor.createSubTaskMonitor(9, false));
220            if (in == null)
221                return null;
222            progressMonitor.subTask(tr("Downloading OSM data..."));
223            return OsmChangeReader.parseDataSet(compression.getUncompressedInputStream(in), progressMonitor.createSubTaskMonitor(1, false));
224        }
225    }
226
227    protected class GpxParser extends Parser<GpxData> {
228        protected GpxParser(ProgressMonitor progressMonitor, Compression compression) {
229            super(progressMonitor, compression);
230        }
231
232        @Override
233        public GpxData parse() throws OsmTransferException, IllegalDataException, IOException, SAXException {
234            in = getInputStreamRaw(url, progressMonitor.createSubTaskMonitor(1, true), null, true);
235            if (in == null)
236                return null;
237            progressMonitor.subTask(tr("Downloading OSM data..."));
238            GpxReader reader = new GpxReader(compression.getUncompressedInputStream(in));
239            gpxParsedProperly = reader.parse(false);
240            GpxData result = reader.getGpxData();
241            result.fromServer = isGpxFromServer(url);
242            return result;
243        }
244    }
245
246    protected class NoteParser extends Parser<List<Note>> {
247
248        public NoteParser(ProgressMonitor progressMonitor, Compression compression) {
249            super(progressMonitor, compression);
250        }
251
252        @Override
253        public List<Note> parse() throws OsmTransferException, IllegalDataException, IOException, SAXException {
254            in = getInputStream(url, progressMonitor.createSubTaskMonitor(1, true));
255            if (in == null) {
256                return Collections.emptyList();
257            }
258            progressMonitor.subTask(tr("Downloading OSM notes..."));
259            NoteReader reader = new NoteReader(compression.getUncompressedInputStream(in));
260            return reader.parse();
261        }
262    }
263
264    /**
265     * Determines if the given URL denotes an OSM gpx-related API call.
266     * @param url The url to check
267     * @return true if the url matches "Trace ID" API call or "Trackpoints bbox" API call, false otherwise
268     * @see GpxData#fromServer
269     * @since 12679
270     */
271    public static final boolean isGpxFromServer(String url) {
272        return url != null && (url.matches(GpxUrlPattern.TRACE_ID.pattern()) || url.matches(GpxUrlPattern.TRACKPOINTS_BBOX.pattern()));
273    }
274}