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.net.HttpURLConnection;
009import java.net.MalformedURLException;
010import java.net.URL;
011import java.util.List;
012
013import org.openstreetmap.josm.Main;
014import org.openstreetmap.josm.data.gpx.GpxData;
015import org.openstreetmap.josm.data.notes.Note;
016import org.openstreetmap.josm.data.osm.DataSet;
017import org.openstreetmap.josm.gui.progress.ProgressMonitor;
018import org.openstreetmap.josm.io.auth.CredentialsAgentException;
019import org.openstreetmap.josm.io.auth.CredentialsManager;
020import org.openstreetmap.josm.tools.HttpClient;
021
022/**
023 * This DataReader reads directly from the REST API of the osm server.
024 *
025 * It supports plain text transfer as well as gzip or deflate encoded transfers;
026 * if compressed transfers are unwanted, set property osm-server.use-compression
027 * to false.
028 *
029 * @author imi
030 */
031public abstract class OsmServerReader extends OsmConnection {
032    private final OsmApi api = OsmApi.getOsmApi();
033    private boolean doAuthenticate;
034    protected boolean gpxParsedProperly;
035
036    /**
037     * Constructs a new {@code OsmServerReader}.
038     */
039    public OsmServerReader() {
040        try {
041            doAuthenticate = OsmApi.isUsingOAuth() && CredentialsManager.getInstance().lookupOAuthAccessToken() != null;
042        } catch (CredentialsAgentException e) {
043            Main.warn(e);
044        }
045    }
046
047    /**
048     * Open a connection to the given url and return a reader on the input stream
049     * from that connection. In case of user cancel, return <code>null</code>.
050     * Relative URL's are directed to API base URL.
051     * @param urlStr The url to connect to.
052     * @param progressMonitor progress monitoring and abort handler
053     * @return A reader reading the input stream (servers answer) or <code>null</code>.
054     * @throws OsmTransferException if data transfer errors occur
055     */
056    protected InputStream getInputStream(String urlStr, ProgressMonitor progressMonitor) throws OsmTransferException {
057        return getInputStream(urlStr, progressMonitor, null);
058    }
059
060    /**
061     * Open a connection to the given url and return a reader on the input stream
062     * from that connection. In case of user cancel, return <code>null</code>.
063     * Relative URL's are directed to API base URL.
064     * @param urlStr The url to connect to.
065     * @param progressMonitor progress monitoring and abort handler
066     * @param reason The reason to show on console. Can be {@code null} if no reason is given
067     * @return A reader reading the input stream (servers answer) or <code>null</code>.
068     * @throws OsmTransferException if data transfer errors occur
069     */
070    protected InputStream getInputStream(String urlStr, ProgressMonitor progressMonitor, String reason) throws OsmTransferException {
071        try {
072            api.initialize(progressMonitor);
073            String url = urlStr.startsWith("http") ? urlStr : (getBaseUrl() + urlStr);
074            return getInputStreamRaw(url, progressMonitor, reason);
075        } finally {
076            progressMonitor.invalidate();
077        }
078    }
079
080    /**
081     * Return the base URL for relative URL requests
082     * @return base url of API
083     */
084    protected String getBaseUrl() {
085        return api.getBaseUrl();
086    }
087
088    /**
089     * Open a connection to the given url and return a reader on the input stream
090     * from that connection. In case of user cancel, return <code>null</code>.
091     * @param urlStr The exact url to connect to.
092     * @param progressMonitor progress monitoring and abort handler
093     * @return An reader reading the input stream (servers answer) or <code>null</code>.
094     * @throws OsmTransferException if data transfer errors occur
095     */
096    protected InputStream getInputStreamRaw(String urlStr, ProgressMonitor progressMonitor) throws OsmTransferException {
097        return getInputStreamRaw(urlStr, progressMonitor, null);
098    }
099
100    /**
101     * Open a connection to the given url and return a reader on the input stream
102     * from that connection. In case of user cancel, return <code>null</code>.
103     * @param urlStr The exact url to connect to.
104     * @param progressMonitor progress monitoring and abort handler
105     * @param reason The reason to show on console. Can be {@code null} if no reason is given
106     * @return An reader reading the input stream (servers answer) or <code>null</code>.
107     * @throws OsmTransferException if data transfer errors occur
108     */
109    protected InputStream getInputStreamRaw(String urlStr, ProgressMonitor progressMonitor, String reason) throws OsmTransferException {
110        return getInputStreamRaw(urlStr, progressMonitor, reason, false);
111    }
112
113    /**
114     * Open a connection to the given url and return a reader on the input stream
115     * from that connection. In case of user cancel, return <code>null</code>.
116     * @param urlStr The exact url to connect to.
117     * @param progressMonitor progress monitoring and abort handler
118     * @param reason The reason to show on console. Can be {@code null} if no reason is given
119     * @param uncompressAccordingToContentDisposition Whether to inspect the HTTP header {@code Content-Disposition}
120     *                                                for {@code filename} and uncompress a gzip/bzip2 stream.
121     * @return An reader reading the input stream (servers answer) or <code>null</code>.
122     * @throws OsmTransferException if data transfer errors occur
123     */
124    @SuppressWarnings("resource")
125    protected InputStream getInputStreamRaw(String urlStr, ProgressMonitor progressMonitor, String reason,
126            boolean uncompressAccordingToContentDisposition) throws OsmTransferException {
127        try {
128            OnlineResource.JOSM_WEBSITE.checkOfflineAccess(urlStr, Main.getJOSMWebsite());
129            OnlineResource.OSM_API.checkOfflineAccess(urlStr, OsmApi.getOsmApi().getServerUrl());
130
131            URL url = null;
132            try {
133                url = new URL(urlStr.replace(" ", "%20"));
134            } catch (MalformedURLException e) {
135                throw new OsmTransferException(e);
136            }
137
138            if ("file".equals(url.getProtocol())) {
139                try {
140                    return url.openStream();
141                } catch (IOException e) {
142                    throw new OsmTransferException(e);
143                }
144            }
145
146            final HttpClient client = HttpClient.create(url);
147            activeConnection = client;
148            client.setReasonForRequest(reason);
149            adaptRequest(client);
150            if (doAuthenticate) {
151                addAuth(client);
152            }
153            if (cancel)
154                throw new OsmTransferCanceledException("Operation canceled");
155
156            final HttpClient.Response response;
157            try {
158                response = client.connect(progressMonitor);
159            } catch (IOException e) {
160                Main.error(e);
161                OsmTransferException ote = new OsmTransferException(
162                        tr("Could not connect to the OSM server. Please check your internet connection."), e);
163                ote.setUrl(url.toString());
164                throw ote;
165            }
166            try {
167                if (response.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED)
168                    throw new OsmApiException(HttpURLConnection.HTTP_UNAUTHORIZED, null, null);
169
170                if (response.getResponseCode() == HttpURLConnection.HTTP_PROXY_AUTH)
171                    throw new OsmTransferCanceledException("Proxy Authentication Required");
172
173                if (response.getResponseCode() != HttpURLConnection.HTTP_OK) {
174                    String errorHeader = response.getHeaderField("Error");
175                    String errorBody = fetchResponseText(response);
176                    throw new OsmApiException(response.getResponseCode(), errorHeader, errorBody, url.toString());
177                }
178
179                response.uncompressAccordingToContentDisposition(uncompressAccordingToContentDisposition);
180                return response.getContent();
181            } catch (OsmTransferException e) {
182                throw e;
183            } catch (IOException e) {
184                throw new OsmTransferException(e);
185            }
186        } finally {
187            progressMonitor.invalidate();
188        }
189    }
190
191    private static String fetchResponseText(final HttpClient.Response response) {
192        try {
193            return response.fetchContent();
194        } catch (IOException e) {
195            Main.error(e);
196            return tr("Reading error text failed.");
197        }
198    }
199
200    /**
201     * Allows subclasses to modify the request.
202     * @param request the prepared request
203     * @since 9308
204     */
205    protected void adaptRequest(HttpClient request) {
206    }
207
208    /**
209     * Download OSM files from somewhere
210     * @param progressMonitor The progress monitor
211     * @return The corresponding dataset
212     * @throws OsmTransferException if any error occurs
213     */
214    public abstract DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException;
215
216    /**
217     * Download OSM Change files from somewhere
218     * @param progressMonitor The progress monitor
219     * @return The corresponding dataset
220     * @throws OsmTransferException if any error occurs
221     */
222    public DataSet parseOsmChange(final ProgressMonitor progressMonitor) throws OsmTransferException {
223        return null;
224    }
225
226    /**
227     * Download BZip2-compressed OSM Change files from somewhere
228     * @param progressMonitor The progress monitor
229     * @return The corresponding dataset
230     * @throws OsmTransferException if any error occurs
231     */
232    public DataSet parseOsmChangeBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException {
233        return null;
234    }
235
236    /**
237     * Download GZip-compressed OSM Change files from somewhere
238     * @param progressMonitor The progress monitor
239     * @return The corresponding dataset
240     * @throws OsmTransferException if any error occurs
241     */
242    public DataSet parseOsmChangeGzip(final ProgressMonitor progressMonitor) throws OsmTransferException {
243        return null;
244    }
245
246    /**
247     * Retrieve raw gps waypoints from the server API.
248     * @param progressMonitor The progress monitor
249     * @return The corresponding GPX tracks
250     * @throws OsmTransferException if any error occurs
251     */
252    public GpxData parseRawGps(final ProgressMonitor progressMonitor) throws OsmTransferException {
253        return null;
254    }
255
256    /**
257     * Retrieve BZip2-compressed GPX files from somewhere.
258     * @param progressMonitor The progress monitor
259     * @return The corresponding GPX tracks
260     * @throws OsmTransferException if any error occurs
261     * @since 6244
262     */
263    public GpxData parseRawGpsBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException {
264        return null;
265    }
266
267    /**
268     * Download BZip2-compressed OSM files from somewhere
269     * @param progressMonitor The progress monitor
270     * @return The corresponding dataset
271     * @throws OsmTransferException if any error occurs
272     */
273    public DataSet parseOsmBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException {
274        return null;
275    }
276
277    /**
278     * Download GZip-compressed OSM files from somewhere
279     * @param progressMonitor The progress monitor
280     * @return The corresponding dataset
281     * @throws OsmTransferException if any error occurs
282     */
283    public DataSet parseOsmGzip(final ProgressMonitor progressMonitor) throws OsmTransferException {
284        return null;
285    }
286
287    /**
288     * Download Zip-compressed OSM files from somewhere
289     * @param progressMonitor The progress monitor
290     * @return The corresponding dataset
291     * @throws OsmTransferException if any error occurs
292     * @since 6882
293     */
294    public DataSet parseOsmZip(final ProgressMonitor progressMonitor) throws OsmTransferException {
295        return null;
296    }
297
298    /**
299     * Returns true if this reader is adding authentication credentials to the read
300     * request sent to the server.
301     *
302     * @return true if this reader is adding authentication credentials to the read
303     * request sent to the server
304     */
305    public boolean isDoAuthenticate() {
306        return doAuthenticate;
307    }
308
309    /**
310     * Sets whether this reader adds authentication credentials to the read
311     * request sent to the server.
312     *
313     * @param doAuthenticate  true if  this reader adds authentication credentials to the read
314     * request sent to the server
315     */
316    public void setDoAuthenticate(boolean doAuthenticate) {
317        this.doAuthenticate = doAuthenticate;
318    }
319
320    /**
321     * Determines if the GPX data has been parsed properly.
322     * @return true if the GPX data has been parsed properly, false otherwise
323     * @see GpxReader#parse
324     */
325    public final boolean isGpxParsedProperly() {
326        return gpxParsedProperly;
327    }
328
329    /**
330     * Downloads notes from the API, given API limit parameters
331     *
332     * @param noteLimit How many notes to download.
333     * @param daysClosed Return notes closed this many days in the past. -1 means all notes, ever. 0 means only unresolved notes.
334     * @param progressMonitor Progress monitor for user feedback
335     * @return List of notes returned by the API
336     * @throws OsmTransferException if any errors happen
337     */
338    public List<Note> parseNotes(int noteLimit, int daysClosed, ProgressMonitor progressMonitor) throws OsmTransferException {
339        return null;
340    }
341
342    /**
343     * Downloads notes from a given raw URL. The URL is assumed to be complete and no API limits are added
344     *
345     * @param progressMonitor progress monitor
346     * @return A list of notes parsed from the URL
347     * @throws OsmTransferException if any error occurs during dialog with OSM API
348     */
349    public List<Note> parseRawNotes(final ProgressMonitor progressMonitor) throws OsmTransferException {
350        return null;
351    }
352
353    /**
354     * Download notes from a URL that contains a bzip2 compressed notes dump file
355     * @param progressMonitor progress monitor
356     * @return A list of notes parsed from the URL
357     * @throws OsmTransferException if any error occurs during dialog with OSM API
358     */
359    public List<Note> parseRawNotesBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException {
360        return null;
361    }
362}