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.nio.charset.StandardCharsets; 014import java.util.List; 015import java.util.Map; 016import java.util.zip.GZIPInputStream; 017import java.util.zip.Inflater; 018import java.util.zip.InflaterInputStream; 019 020import org.openstreetmap.josm.Main; 021import org.openstreetmap.josm.data.gpx.GpxData; 022import org.openstreetmap.josm.data.osm.DataSet; 023import org.openstreetmap.josm.gui.progress.ProgressMonitor; 024import org.openstreetmap.josm.tools.Utils; 025 026/** 027 * This DataReader reads directly from the REST API of the osm server. 028 * 029 * It supports plain text transfer as well as gzip or deflate encoded transfers; 030 * if compressed transfers are unwanted, set property osm-server.use-compression 031 * to false. 032 * 033 * @author imi 034 */ 035public abstract class OsmServerReader extends OsmConnection { 036 private OsmApi api = OsmApi.getOsmApi(); 037 private boolean doAuthenticate = false; 038 protected boolean gpxParsedProperly; 039 040 /** 041 * Open a connection to the given url and return a reader on the input stream 042 * from that connection. In case of user cancel, return <code>null</code>. 043 * Relative URL's are directed to API base URL. 044 * @param urlStr The url to connect to. 045 * @param progressMonitor progress monitoring and abort handler 046 * @return A reader reading the input stream (servers answer) or <code>null</code>. 047 * @throws OsmTransferException thrown if data transfer errors occur 048 */ 049 protected InputStream getInputStream(String urlStr, ProgressMonitor progressMonitor) throws OsmTransferException { 050 return getInputStream(urlStr, progressMonitor, null); 051 } 052 053 /** 054 * Open a connection to the given url and return a reader on the input stream 055 * from that connection. In case of user cancel, return <code>null</code>. 056 * Relative URL's are directed to API base URL. 057 * @param urlStr The url to connect to. 058 * @param progressMonitor progress monitoring and abort handler 059 * @param reason The reason to show on console. Can be {@code null} if no reason is given 060 * @return A reader reading the input stream (servers answer) or <code>null</code>. 061 * @throws OsmTransferException thrown if data transfer errors occur 062 */ 063 protected InputStream getInputStream(String urlStr, ProgressMonitor progressMonitor, String reason) throws OsmTransferException { 064 try { 065 api.initialize(progressMonitor); 066 String url = urlStr.startsWith("http") ? urlStr : (getBaseUrl() + urlStr); 067 return getInputStreamRaw(url, progressMonitor, reason); 068 } finally { 069 progressMonitor.invalidate(); 070 } 071 } 072 073 /** 074 * Return the base URL for relative URL requests 075 * @return base url of API 076 */ 077 protected String getBaseUrl() { 078 return api.getBaseUrl(); 079 } 080 081 /** 082 * Open a connection to the given url and return a reader on the input stream 083 * from that connection. In case of user cancel, return <code>null</code>. 084 * @param urlStr The exact url to connect to. 085 * @param progressMonitor progress monitoring and abort handler 086 * @return An reader reading the input stream (servers answer) or <code>null</code>. 087 * @throws OsmTransferException thrown if data transfer errors occur 088 */ 089 protected InputStream getInputStreamRaw(String urlStr, ProgressMonitor progressMonitor) throws OsmTransferException { 090 return getInputStreamRaw(urlStr, progressMonitor, null); 091 } 092 093 /** 094 * Open a connection to the given url and return a reader on the input stream 095 * from that connection. In case of user cancel, return <code>null</code>. 096 * @param urlStr The exact url to connect to. 097 * @param progressMonitor progress monitoring and abort handler 098 * @param reason The reason to show on console. Can be {@code null} if no reason is given 099 * @return An reader reading the input stream (servers answer) or <code>null</code>. 100 * @throws OsmTransferException thrown if data transfer errors occur 101 */ 102 protected InputStream getInputStreamRaw(String urlStr, ProgressMonitor progressMonitor, String reason) throws OsmTransferException { 103 return getInputStreamRaw(urlStr, progressMonitor, reason, false); 104 } 105 106 /** 107 * Open a connection to the given url and return a reader on the input stream 108 * from that connection. In case of user cancel, return <code>null</code>. 109 * @param urlStr The exact url to connect to. 110 * @param progressMonitor progress monitoring and abort handler 111 * @param reason The reason to show on console. Can be {@code null} if no reason is given 112 * @param uncompressAccordingToContentDisposition Whether to inspect the HTTP header {@code Content-Disposition} 113 * for {@code filename} and uncompress a gzip/bzip2 stream. 114 * @return An reader reading the input stream (servers answer) or <code>null</code>. 115 * @throws OsmTransferException thrown if data transfer errors occur 116 */ 117 @SuppressWarnings("resource") 118 protected InputStream getInputStreamRaw(String urlStr, ProgressMonitor progressMonitor, String reason, boolean uncompressAccordingToContentDisposition) throws OsmTransferException { 119 try { 120 URL url = null; 121 try { 122 url = new URL(urlStr.replace(" ", "%20")); 123 } catch(MalformedURLException e) { 124 throw new OsmTransferException(e); 125 } 126 try { 127 // fix #7640, see http://www.tikalk.com/java/forums/httpurlconnection-disable-keep-alive 128 activeConnection = Utils.openHttpConnection(url, false); 129 } catch(Exception e) { 130 throw new OsmTransferException(tr("Failed to open connection to API {0}.", url.toExternalForm()), e); 131 } 132 if (cancel) { 133 activeConnection.disconnect(); 134 return null; 135 } 136 137 if (doAuthenticate) { 138 addAuth(activeConnection); 139 } 140 if (cancel) 141 throw new OsmTransferCanceledException(); 142 if (Main.pref.getBoolean("osm-server.use-compression", true)) { 143 activeConnection.setRequestProperty("Accept-Encoding", "gzip, deflate"); 144 } 145 146 activeConnection.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000); 147 148 try { 149 if (reason != null && !reason.isEmpty()) { 150 Main.info("GET " + url + " (" + reason + ")"); 151 } else { 152 Main.info("GET " + url); 153 } 154 activeConnection.connect(); 155 } catch (Exception e) { 156 Main.error(e); 157 OsmTransferException ote = new OsmTransferException(tr("Could not connect to the OSM server. Please check your internet connection."), e); 158 ote.setUrl(url.toString()); 159 throw ote; 160 } 161 try { 162 if (Main.isDebugEnabled()) { 163 Main.debug("RESPONSE: "+activeConnection.getHeaderFields()); 164 } 165 if (activeConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) 166 throw new OsmApiException(HttpURLConnection.HTTP_UNAUTHORIZED,null,null); 167 168 if (activeConnection.getResponseCode() == HttpURLConnection.HTTP_PROXY_AUTH) 169 throw new OsmTransferCanceledException(); 170 171 String encoding = activeConnection.getContentEncoding(); 172 if (activeConnection.getResponseCode() != HttpURLConnection.HTTP_OK) { 173 String errorHeader = activeConnection.getHeaderField("Error"); 174 StringBuilder errorBody = new StringBuilder(); 175 try { 176 InputStream i = fixEncoding(activeConnection.getErrorStream(), encoding); 177 if (i != null) { 178 BufferedReader in = new BufferedReader(new InputStreamReader(i, StandardCharsets.UTF_8)); 179 String s; 180 while((s = in.readLine()) != null) { 181 errorBody.append(s); 182 errorBody.append("\n"); 183 } 184 } 185 } 186 catch(Exception e) { 187 errorBody.append(tr("Reading error text failed.")); 188 } 189 190 throw new OsmApiException(activeConnection.getResponseCode(), errorHeader, errorBody.toString(), url.toString()); 191 } 192 193 InputStream in = new ProgressInputStream(activeConnection, progressMonitor); 194 if (uncompressAccordingToContentDisposition) { 195 in = uncompressAccordingToContentDisposition(in, activeConnection.getHeaderFields()); 196 } 197 return fixEncoding(in, encoding); 198 } catch (OsmTransferException e) { 199 throw e; 200 } catch (Exception e) { 201 throw new OsmTransferException(e); 202 } 203 } finally { 204 progressMonitor.invalidate(); 205 } 206 } 207 208 private InputStream fixEncoding(InputStream stream, String encoding) throws IOException { 209 if ("gzip".equalsIgnoreCase(encoding)) { 210 stream = new GZIPInputStream(stream); 211 } else if ("deflate".equalsIgnoreCase(encoding)) { 212 stream = new InflaterInputStream(stream, new Inflater(true)); 213 } 214 return stream; 215 } 216 217 private InputStream uncompressAccordingToContentDisposition(InputStream stream, Map<String, List<String>> headerFields) throws IOException { 218 List<String> field = headerFields.get("Content-Disposition"); 219 if (field != null && field.toString().contains(".gz\"")) { 220 return Compression.GZIP.getUncompressedInputStream(stream); 221 } else if (field != null && field.toString().contains(".bz2\"")) { 222 return Compression.BZIP2.getUncompressedInputStream(stream); 223 } else { 224 return stream; 225 } 226 } 227 228 /** 229 * Download 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 abstract DataSet parseOsm(final ProgressMonitor progressMonitor) throws OsmTransferException; 235 236 /** 237 * Download 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 parseOsmChange(final ProgressMonitor progressMonitor) throws OsmTransferException { 243 return null; 244 } 245 246 /** 247 * Download BZip2-compressed OSM Change files from somewhere 248 * @param progressMonitor The progress monitor 249 * @return The corresponding dataset 250 * @throws OsmTransferException if any error occurs 251 */ 252 public DataSet parseOsmChangeBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException { 253 return null; 254 } 255 256 /** 257 * Download GZip-compressed OSM Change files from somewhere 258 * @param progressMonitor The progress monitor 259 * @return The corresponding dataset 260 * @throws OsmTransferException if any error occurs 261 */ 262 public DataSet parseOsmChangeGzip(final ProgressMonitor progressMonitor) throws OsmTransferException { 263 return null; 264 } 265 266 /** 267 * Retrieve raw gps waypoints from the server API. 268 * @param progressMonitor The progress monitor 269 * @return The corresponding GPX tracks 270 * @throws OsmTransferException if any error occurs 271 */ 272 public GpxData parseRawGps(final ProgressMonitor progressMonitor) throws OsmTransferException { 273 return null; 274 } 275 276 /** 277 * Retrieve BZip2-compressed GPX files from somewhere. 278 * @param progressMonitor The progress monitor 279 * @return The corresponding GPX tracks 280 * @throws OsmTransferException if any error occurs 281 * @since 6244 282 */ 283 public GpxData parseRawGpsBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException { 284 return null; 285 } 286 287 /** 288 * Download BZip2-compressed OSM files from somewhere 289 * @param progressMonitor The progress monitor 290 * @return The corresponding dataset 291 * @throws OsmTransferException if any error occurs 292 */ 293 public DataSet parseOsmBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException { 294 return null; 295 } 296 297 /** 298 * Download GZip-compressed OSM files from somewhere 299 * @param progressMonitor The progress monitor 300 * @return The corresponding dataset 301 * @throws OsmTransferException if any error occurs 302 */ 303 public DataSet parseOsmGzip(final ProgressMonitor progressMonitor) throws OsmTransferException { 304 return null; 305 } 306 307 /** 308 * Download Zip-compressed OSM files from somewhere 309 * @param progressMonitor The progress monitor 310 * @return The corresponding dataset 311 * @throws OsmTransferException if any error occurs 312 * @since 6882 313 */ 314 public DataSet parseOsmZip(final ProgressMonitor progressMonitor) throws OsmTransferException { 315 return null; 316 } 317 318 /** 319 * Returns true if this reader is adding authentication credentials to the read 320 * request sent to the server. 321 * 322 * @return true if this reader is adding authentication credentials to the read 323 * request sent to the server 324 */ 325 public boolean isDoAuthenticate() { 326 return doAuthenticate; 327 } 328 329 /** 330 * Sets whether this reader adds authentication credentials to the read 331 * request sent to the server. 332 * 333 * @param doAuthenticate true if this reader adds authentication credentials to the read 334 * request sent to the server 335 */ 336 public void setDoAuthenticate(boolean doAuthenticate) { 337 this.doAuthenticate = doAuthenticate; 338 } 339 340 /** 341 * Determines if the GPX data has been parsed properly. 342 * @return true if the GPX data has been parsed properly, false otherwise 343 * @see GpxReader#parse 344 */ 345 public final boolean isGpxParsedProperly() { 346 return gpxParsedProperly; 347 } 348}