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.BufferedReader;
007import java.io.IOException;
008import java.io.InputStream;
009import java.io.InputStreamReader;
010import java.net.HttpURLConnection;
011import java.net.MalformedURLException;
012import java.net.URL;
013import java.util.zip.GZIPInputStream;
014import java.util.zip.Inflater;
015import java.util.zip.InflaterInputStream;
016
017import org.openstreetmap.josm.Main;
018import org.openstreetmap.josm.data.gpx.GpxData;
019import org.openstreetmap.josm.data.osm.DataSet;
020import org.openstreetmap.josm.gui.progress.ProgressMonitor;
021import org.openstreetmap.josm.tools.Utils;
022
023/**
024 * This DataReader reads directly from the REST API of the osm server.
025 *
026 * It supports plain text transfer as well as gzip or deflate encoded transfers;
027 * if compressed transfers are unwanted, set property osm-server.use-compression
028 * to false.
029 *
030 * @author imi
031 */
032public abstract class OsmServerReader extends OsmConnection {
033    private OsmApi api = OsmApi.getOsmApi();
034    private boolean doAuthenticate = false;
035    protected boolean gpxParsedProperly;
036    
037    protected enum Compression {
038        NONE,
039        BZIP2,
040        GZIP
041    }
042
043    /**
044     * Open a connection to the given url and return a reader on the input stream
045     * from that connection. In case of user cancel, return <code>null</code>.
046     * Relative URL's are directed to API base URL.
047     * @param urlStr The url to connect to.
048     * @param progressMonitor progress monitoring and abort handler
049     * @return An reader reading the input stream (servers answer) or <code>null</code>.
050     * @throws OsmTransferException thrown if data transfer errors occur
051     */
052    protected InputStream getInputStream(String urlStr, ProgressMonitor progressMonitor) throws OsmTransferException  {
053        try {
054            api.initialize(progressMonitor);
055            urlStr = urlStr.startsWith("http") ? urlStr : (getBaseUrl() + urlStr);
056            return getInputStreamRaw(urlStr, progressMonitor);
057        } finally {
058            progressMonitor.invalidate();
059        }
060    }
061
062    /**
063     * Retrun the base URL for relative URL requests
064     * @return base url of API
065     */
066    protected String getBaseUrl() {
067        return api.getBaseUrl();
068    }
069
070    /**
071     * Open a connection to the given url and return a reader on the input stream
072     * from that connection. In case of user cancel, return <code>null</code>.
073     * @param urlStr The exact url to connect to.
074     * @param progressMonitor progress monitoring and abort handler
075     * @return An reader reading the input stream (servers answer) or <code>null</code>.
076     * @throws OsmTransferException thrown if data transfer errors occur
077     */
078    protected InputStream getInputStreamRaw(String urlStr, ProgressMonitor progressMonitor) throws OsmTransferException {
079        try {
080            URL url = null;
081            try {
082                url = new URL(urlStr.replace(" ", "%20"));
083            } catch(MalformedURLException e) {
084                throw new OsmTransferException(e);
085            }
086            try {
087                // fix #7640, see http://www.tikalk.com/java/forums/httpurlconnection-disable-keep-alive
088                activeConnection = Utils.openHttpConnection(url, false);
089            } catch(Exception e) {
090                throw new OsmTransferException(tr("Failed to open connection to API {0}.", url.toExternalForm()), e);
091            }
092            if (cancel) {
093                activeConnection.disconnect();
094                return null;
095            }
096
097            if (doAuthenticate) {
098                addAuth(activeConnection);
099            }
100            if (cancel)
101                throw new OsmTransferCanceledException();
102            if (Main.pref.getBoolean("osm-server.use-compression", true)) {
103                activeConnection.setRequestProperty("Accept-Encoding", "gzip, deflate");
104            }
105
106            activeConnection.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000);
107
108            try {
109                Main.info("GET " + url);
110                activeConnection.connect();
111            } catch (Exception e) {
112                e.printStackTrace();
113                OsmTransferException ote = new OsmTransferException(tr("Could not connect to the OSM server. Please check your internet connection."), e);
114                ote.setUrl(url.toString());
115                throw ote;
116            }
117            try {
118                if (activeConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED)
119                    throw new OsmApiException(HttpURLConnection.HTTP_UNAUTHORIZED,null,null);
120
121                if (activeConnection.getResponseCode() == HttpURLConnection.HTTP_PROXY_AUTH)
122                    throw new OsmTransferCanceledException();
123
124                String encoding = activeConnection.getContentEncoding();
125                if (activeConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
126                    String errorHeader = activeConnection.getHeaderField("Error");
127                    StringBuilder errorBody = new StringBuilder();
128                    try
129                    {
130                        InputStream i = FixEncoding(activeConnection.getErrorStream(), encoding);
131                        if (i != null) {
132                            BufferedReader in = new BufferedReader(new InputStreamReader(i));
133                            String s;
134                            while((s = in.readLine()) != null) {
135                                errorBody.append(s);
136                                errorBody.append("\n");
137                            }
138                        }
139                    }
140                    catch(Exception e) {
141                        errorBody.append(tr("Reading error text failed."));
142                    }
143
144                    throw new OsmApiException(activeConnection.getResponseCode(), errorHeader, errorBody.toString(), url.toString());
145                }
146
147                return FixEncoding(new ProgressInputStream(activeConnection, progressMonitor), encoding);
148            } catch (OsmTransferException e) {
149                throw e;
150            } catch (Exception e) {
151                throw new OsmTransferException(e);
152            }
153        } finally {
154            progressMonitor.invalidate();
155        }
156    }
157
158    private InputStream FixEncoding(InputStream stream, String encoding) throws IOException
159    {
160        if ("gzip".equalsIgnoreCase(encoding)) {
161            stream = new GZIPInputStream(stream);
162        }
163        else if ("deflate".equalsIgnoreCase(encoding)) {
164            stream = new InflaterInputStream(stream, new Inflater(true));
165        }
166        return stream;
167    }
168
169    /**
170     * Download OSM files from somewhere
171     * @param progressMonitor The progress monitor
172     * @return The corresponding dataset
173     * @throws OsmTransferException if any error occurs 
174     */
175    public abstract DataSet parseOsm(final ProgressMonitor progressMonitor) throws OsmTransferException;
176
177    /**
178     * Download OSM Change files from somewhere
179     * @param progressMonitor The progress monitor
180     * @return The corresponding dataset
181     * @throws OsmTransferException if any error occurs 
182     */
183    public DataSet parseOsmChange(final ProgressMonitor progressMonitor) throws OsmTransferException {
184        return null;
185    }
186
187    /**
188     * Download BZip2-compressed OSM Change files from somewhere
189     * @param progressMonitor The progress monitor
190     * @return The corresponding dataset
191     * @throws OsmTransferException if any error occurs 
192     */
193    public DataSet parseOsmChangeBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException {
194        return null;
195    }
196
197    /**
198     * Download GZip-compressed OSM Change files from somewhere
199     * @param progressMonitor The progress monitor
200     * @return The corresponding dataset
201     * @throws OsmTransferException if any error occurs 
202     */
203    public DataSet parseOsmChangeGzip(final ProgressMonitor progressMonitor) throws OsmTransferException {
204        return null;
205    }
206
207    /**
208     * Retrieve raw gps waypoints from the server API.
209     * @param progressMonitor The progress monitor
210     * @return The corresponding GPX tracks
211     * @throws OsmTransferException if any error occurs
212     */
213    public GpxData parseRawGps(final ProgressMonitor progressMonitor) throws OsmTransferException {
214        return null;
215    }
216
217    /**
218     * Retrieve BZip2-compressed GPX files from somewhere.
219     * @param progressMonitor The progress monitor
220     * @return The corresponding GPX tracks
221     * @throws OsmTransferException if any error occurs
222     * @since 6244
223     */
224    public GpxData parseRawGpsBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException {
225        return null;
226    }
227
228    /**
229     * Download BZip2-compressed OSM files from somewhere
230     * @param progressMonitor The progress monitor
231     * @return The corresponding dataset
232     * @throws OsmTransferException if any error occurs 
233     */
234    public DataSet parseOsmBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException {
235        return null;
236    }
237
238    /**
239     * Download GZip-compressed OSM files from somewhere
240     * @param progressMonitor The progress monitor
241     * @return The corresponding dataset
242     * @throws OsmTransferException if any error occurs 
243     */
244    public DataSet parseOsmGzip(final ProgressMonitor progressMonitor) throws OsmTransferException {
245        return null;
246    }
247
248    /**
249     * Returns true if this reader is adding authentication credentials to the read
250     * request sent to the server.
251     *
252     * @return true if this reader is adding authentication credentials to the read
253     * request sent to the server
254     */
255    public boolean isDoAuthenticate() {
256        return doAuthenticate;
257    }
258
259    /**
260     * Sets whether this reader adds authentication credentials to the read
261     * request sent to the server.
262     *
263     * @param doAuthenticate  true if  this reader adds authentication credentials to the read
264     * request sent to the server
265     */
266    public void setDoAuthenticate(boolean doAuthenticate) {
267        this.doAuthenticate = doAuthenticate;
268    }
269
270    /**
271     * Determines if the GPX data has been parsed properly.
272     * @return true if the GPX data has been parsed properly, false otherwise
273     * @see GpxReader#parse
274     */
275    public final boolean isGpxParsedProperly() {
276        return gpxParsedProperly;
277    }
278}