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 OnlineResource.JOSM_WEBSITE.checkOfflineAccess(urlStr, Main.getJOSMWebsite()); 121 OnlineResource.OSM_API.checkOfflineAccess(urlStr, Main.pref.get("osm-server.url", OsmApi.DEFAULT_API_URL)); 122 123 URL url = null; 124 try { 125 url = new URL(urlStr.replace(" ", "%20")); 126 } catch(MalformedURLException e) { 127 throw new OsmTransferException(e); 128 } 129 try { 130 // fix #7640, see http://www.tikalk.com/java/forums/httpurlconnection-disable-keep-alive 131 activeConnection = Utils.openHttpConnection(url, false); 132 } catch(Exception e) { 133 throw new OsmTransferException(tr("Failed to open connection to API {0}.", url.toExternalForm()), e); 134 } 135 if (cancel) { 136 activeConnection.disconnect(); 137 return null; 138 } 139 140 if (doAuthenticate) { 141 addAuth(activeConnection); 142 } 143 if (cancel) 144 throw new OsmTransferCanceledException(); 145 if (Main.pref.getBoolean("osm-server.use-compression", true)) { 146 activeConnection.setRequestProperty("Accept-Encoding", "gzip, deflate"); 147 } 148 149 activeConnection.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000); 150 151 try { 152 if (reason != null && !reason.isEmpty()) { 153 Main.info("GET " + url + " (" + reason + ")"); 154 } else { 155 Main.info("GET " + url); 156 } 157 activeConnection.connect(); 158 } catch (Exception e) { 159 Main.error(e); 160 OsmTransferException ote = new OsmTransferException(tr("Could not connect to the OSM server. Please check your internet connection."), e); 161 ote.setUrl(url.toString()); 162 throw ote; 163 } 164 try { 165 if (Main.isDebugEnabled()) { 166 Main.debug("RESPONSE: "+activeConnection.getHeaderFields()); 167 } 168 if (activeConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) 169 throw new OsmApiException(HttpURLConnection.HTTP_UNAUTHORIZED,null,null); 170 171 if (activeConnection.getResponseCode() == HttpURLConnection.HTTP_PROXY_AUTH) 172 throw new OsmTransferCanceledException(); 173 174 String encoding = activeConnection.getContentEncoding(); 175 if (activeConnection.getResponseCode() != HttpURLConnection.HTTP_OK) { 176 String errorHeader = activeConnection.getHeaderField("Error"); 177 StringBuilder errorBody = new StringBuilder(); 178 try { 179 InputStream i = fixEncoding(activeConnection.getErrorStream(), encoding); 180 if (i != null) { 181 BufferedReader in = new BufferedReader(new InputStreamReader(i, StandardCharsets.UTF_8)); 182 String s; 183 while((s = in.readLine()) != null) { 184 errorBody.append(s); 185 errorBody.append("\n"); 186 } 187 } 188 } 189 catch(Exception e) { 190 errorBody.append(tr("Reading error text failed.")); 191 } 192 193 throw new OsmApiException(activeConnection.getResponseCode(), errorHeader, errorBody.toString(), url.toString()); 194 } 195 196 InputStream in = new ProgressInputStream(activeConnection, progressMonitor); 197 if (uncompressAccordingToContentDisposition) { 198 in = uncompressAccordingToContentDisposition(in, activeConnection.getHeaderFields()); 199 } 200 return fixEncoding(in, encoding); 201 } catch (OsmTransferException e) { 202 throw e; 203 } catch (Exception e) { 204 throw new OsmTransferException(e); 205 } 206 } finally { 207 progressMonitor.invalidate(); 208 } 209 } 210 211 private InputStream fixEncoding(InputStream stream, String encoding) throws IOException { 212 if ("gzip".equalsIgnoreCase(encoding)) { 213 stream = new GZIPInputStream(stream); 214 } else if ("deflate".equalsIgnoreCase(encoding)) { 215 stream = new InflaterInputStream(stream, new Inflater(true)); 216 } 217 return stream; 218 } 219 220 private InputStream uncompressAccordingToContentDisposition(InputStream stream, Map<String, List<String>> headerFields) throws IOException { 221 List<String> field = headerFields.get("Content-Disposition"); 222 if (field != null && field.toString().contains(".gz\"")) { 223 return Compression.GZIP.getUncompressedInputStream(stream); 224 } else if (field != null && field.toString().contains(".bz2\"")) { 225 return Compression.BZIP2.getUncompressedInputStream(stream); 226 } else { 227 return stream; 228 } 229 } 230 231 /** 232 * Download OSM files from somewhere 233 * @param progressMonitor The progress monitor 234 * @return The corresponding dataset 235 * @throws OsmTransferException if any error occurs 236 */ 237 public abstract DataSet parseOsm(final ProgressMonitor progressMonitor) throws OsmTransferException; 238 239 /** 240 * Download OSM Change files from somewhere 241 * @param progressMonitor The progress monitor 242 * @return The corresponding dataset 243 * @throws OsmTransferException if any error occurs 244 */ 245 public DataSet parseOsmChange(final ProgressMonitor progressMonitor) throws OsmTransferException { 246 return null; 247 } 248 249 /** 250 * Download BZip2-compressed OSM Change files from somewhere 251 * @param progressMonitor The progress monitor 252 * @return The corresponding dataset 253 * @throws OsmTransferException if any error occurs 254 */ 255 public DataSet parseOsmChangeBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException { 256 return null; 257 } 258 259 /** 260 * Download GZip-compressed OSM Change files from somewhere 261 * @param progressMonitor The progress monitor 262 * @return The corresponding dataset 263 * @throws OsmTransferException if any error occurs 264 */ 265 public DataSet parseOsmChangeGzip(final ProgressMonitor progressMonitor) throws OsmTransferException { 266 return null; 267 } 268 269 /** 270 * Retrieve raw gps waypoints from the server API. 271 * @param progressMonitor The progress monitor 272 * @return The corresponding GPX tracks 273 * @throws OsmTransferException if any error occurs 274 */ 275 public GpxData parseRawGps(final ProgressMonitor progressMonitor) throws OsmTransferException { 276 return null; 277 } 278 279 /** 280 * Retrieve BZip2-compressed GPX files from somewhere. 281 * @param progressMonitor The progress monitor 282 * @return The corresponding GPX tracks 283 * @throws OsmTransferException if any error occurs 284 * @since 6244 285 */ 286 public GpxData parseRawGpsBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException { 287 return null; 288 } 289 290 /** 291 * Download BZip2-compressed OSM files from somewhere 292 * @param progressMonitor The progress monitor 293 * @return The corresponding dataset 294 * @throws OsmTransferException if any error occurs 295 */ 296 public DataSet parseOsmBzip2(final ProgressMonitor progressMonitor) throws OsmTransferException { 297 return null; 298 } 299 300 /** 301 * Download GZip-compressed OSM files from somewhere 302 * @param progressMonitor The progress monitor 303 * @return The corresponding dataset 304 * @throws OsmTransferException if any error occurs 305 */ 306 public DataSet parseOsmGzip(final ProgressMonitor progressMonitor) throws OsmTransferException { 307 return null; 308 } 309 310 /** 311 * Download Zip-compressed OSM files from somewhere 312 * @param progressMonitor The progress monitor 313 * @return The corresponding dataset 314 * @throws OsmTransferException if any error occurs 315 * @since 6882 316 */ 317 public DataSet parseOsmZip(final ProgressMonitor progressMonitor) throws OsmTransferException { 318 return null; 319 } 320 321 /** 322 * Returns true if this reader is adding authentication credentials to the read 323 * request sent to the server. 324 * 325 * @return true if this reader is adding authentication credentials to the read 326 * request sent to the server 327 */ 328 public boolean isDoAuthenticate() { 329 return doAuthenticate; 330 } 331 332 /** 333 * Sets whether this reader adds authentication credentials to the read 334 * request sent to the server. 335 * 336 * @param doAuthenticate true if this reader adds authentication credentials to the read 337 * request sent to the server 338 */ 339 public void setDoAuthenticate(boolean doAuthenticate) { 340 this.doAuthenticate = doAuthenticate; 341 } 342 343 /** 344 * Determines if the GPX data has been parsed properly. 345 * @return true if the GPX data has been parsed properly, false otherwise 346 * @see GpxReader#parse 347 */ 348 public final boolean isGpxParsedProperly() { 349 return gpxParsedProperly; 350 } 351}