001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.preferences.server; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.Component; 007 import java.io.BufferedReader; 008 import java.io.IOException; 009 import java.io.InputStreamReader; 010 import java.net.HttpURLConnection; 011 import java.net.MalformedURLException; 012 import java.net.URL; 013 014 import javax.swing.JOptionPane; 015 016 import org.openstreetmap.josm.data.Version; 017 import org.openstreetmap.josm.gui.HelpAwareOptionPane; 018 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 019 import org.openstreetmap.josm.gui.help.HelpUtil; 020 import org.openstreetmap.josm.io.OsmTransferException; 021 import org.openstreetmap.josm.tools.CheckParameterUtil; 022 import org.xml.sax.SAXException; 023 024 /** 025 * This is an asynchronous task for testing whether an URL points to an OSM API server. 026 * It tries to retrieve a list of changesets from the given URL. If it succeeds, the method 027 * {@link #isSuccess()} replies true, otherwise false. 028 * 029 * Note: it fetches a list of changesets instead of the much smaller capabilities because - strangely enough - 030 * an OSM server "http://x.y.y/api/0.6" not only responds to "http://x.y.y/api/0.6/capabilities" but also 031 * to "http://x.y.y/api/0/capabilities" or "http://x.y.y/a/capabilities" with valid capabilities. If we get 032 * valid capabilities with an URL we therefore can't be sure that the base URL is valid API URL. 033 * 034 */ 035 public class ApiUrlTestTask extends PleaseWaitRunnable{ 036 037 private String url; 038 private boolean canceled; 039 private boolean success; 040 private Component parent; 041 private HttpURLConnection connection; 042 043 /** 044 * Creates the task 045 * 046 * @param parent the parent component relative to which the {@link PleaseWaitRunnable}-Dialog is displayed 047 * @param url the url. Must not be null. 048 * @throws IllegalArgumentException thrown if url is null. 049 */ 050 public ApiUrlTestTask(Component parent, String url) throws IllegalArgumentException { 051 super(parent, tr("Testing OSM API URL ''{0}''", url), false /* don't ignore exceptions */); 052 CheckParameterUtil.ensureParameterNotNull(url,"url"); 053 this.parent = parent; 054 this.url = url; 055 } 056 057 protected void alertInvalidUrl(String url) { 058 HelpAwareOptionPane.showOptionDialog( 059 parent, 060 tr("<html>" 061 + "''{0}'' is not a valid OSM API URL.<br>" 062 + "Please check the spelling and validate again." 063 + "</html>", 064 url 065 ), 066 tr("Invalid API URL"), 067 JOptionPane.ERROR_MESSAGE, 068 HelpUtil.ht("/Preferences/Connection#InvalidAPIUrl") 069 ); 070 } 071 072 protected void alertInvalidChangesetUrl(String url) { 073 HelpAwareOptionPane.showOptionDialog( 074 parent, 075 tr("<html>" 076 + "Failed to build URL ''{0}'' for validating the OSM API server.<br>" 077 + "Please check the spelling of ''{1}'' and validate again." 078 +"</html>", 079 url, 080 getNormalizedApiUrl() 081 ), 082 tr("Invalid API URL"), 083 JOptionPane.ERROR_MESSAGE, 084 HelpUtil.ht("/Preferences/Connection#InvalidAPIGetChangesetsUrl") 085 ); 086 } 087 088 protected void alertConnectionFailed() { 089 HelpAwareOptionPane.showOptionDialog( 090 parent, 091 tr("<html>" 092 + "Failed to connect to the URL ''{0}''.<br>" 093 + "Please check the spelling of ''{1}'' and your Internet connection and validate again." 094 +"</html>", 095 url, 096 getNormalizedApiUrl() 097 ), 098 tr("Connection to API failed"), 099 JOptionPane.ERROR_MESSAGE, 100 HelpUtil.ht("/Preferences/Connection#ConnectionToAPIFailed") 101 ); 102 } 103 104 protected void alertInvalidServerResult(int retCode) { 105 HelpAwareOptionPane.showOptionDialog( 106 parent, 107 tr("<html>" 108 + "Failed to retrieve a list of changesets from the OSM API server at<br>" 109 + "''{1}''. The server responded with the return code {0} instead of 200.<br>" 110 + "Please check the spelling of ''{1}'' and validate again." 111 + "</html>", 112 retCode, 113 getNormalizedApiUrl() 114 ), 115 tr("Connection to API failed"), 116 JOptionPane.ERROR_MESSAGE, 117 HelpUtil.ht("/Preferences/Connection#InvalidServerResult") 118 ); 119 } 120 121 protected void alertInvalidChangesetList() { 122 HelpAwareOptionPane.showOptionDialog( 123 parent, 124 tr("<html>" 125 + "The OSM API server at ''{0}'' did not return a valid response.<br>" 126 + "It is likely that ''{0}'' is not an OSM API server.<br>" 127 + "Please check the spelling of ''{0}'' and validate again." 128 + "</html>", 129 getNormalizedApiUrl() 130 ), 131 tr("Connection to API failed"), 132 JOptionPane.ERROR_MESSAGE, 133 HelpUtil.ht("/Preferences/Connection#InvalidSettings") 134 ); 135 } 136 137 @Override 138 protected void cancel() { 139 canceled = true; 140 synchronized(this) { 141 if (connection != null) { 142 connection.disconnect(); 143 } 144 } 145 } 146 147 @Override 148 protected void finish() {} 149 150 /** 151 * Removes leading and trailing whitespace from the API URL and removes trailing 152 * '/'. 153 * 154 * @return the normalized API URL 155 */ 156 protected String getNormalizedApiUrl() { 157 String apiUrl = url.trim(); 158 while(apiUrl.endsWith("/")) { 159 apiUrl = apiUrl.substring(0, apiUrl.lastIndexOf("/")); 160 } 161 return apiUrl; 162 } 163 164 @Override 165 protected void realRun() throws SAXException, IOException, OsmTransferException { 166 BufferedReader bin = null; 167 try { 168 try { 169 new URL(getNormalizedApiUrl()); 170 } catch(MalformedURLException e) { 171 alertInvalidUrl(getNormalizedApiUrl()); 172 return; 173 } 174 URL capabilitiesUrl; 175 String getChangesetsUrl = getNormalizedApiUrl() + "/0.6/changesets"; 176 try { 177 capabilitiesUrl = new URL(getChangesetsUrl); 178 } catch(MalformedURLException e) { 179 alertInvalidChangesetUrl(getChangesetsUrl); 180 return; 181 } 182 183 synchronized(this) { 184 connection = (HttpURLConnection)capabilitiesUrl.openConnection(); 185 } 186 connection.setDoInput(true); 187 connection.setDoOutput(false); 188 connection.setRequestMethod("GET"); 189 connection.setRequestProperty("User-Agent", Version.getInstance().getAgentString()); 190 connection.setRequestProperty("Host", connection.getURL().getHost()); 191 connection.connect(); 192 193 if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { 194 alertInvalidServerResult(connection.getResponseCode()); 195 return; 196 } 197 StringBuilder changesets = new StringBuilder(); 198 bin = new BufferedReader(new InputStreamReader(connection.getInputStream())); 199 String line; 200 while ((line = bin.readLine()) != null) { 201 changesets.append(line).append("\n"); 202 } 203 if (! (changesets.toString().contains("<osm") && changesets.toString().contains("</osm>"))) { 204 // heuristic: if there isn't an opening and closing "<osm>" tag in the returned content, 205 // then we didn't get a list of changesets in return. Could be replaced by explicitly parsing 206 // the result but currently not worth the effort. 207 alertInvalidChangesetList(); 208 return; 209 } 210 success = true; 211 } catch(IOException e) { 212 if (canceled) 213 // ignore exceptions 214 return; 215 e.printStackTrace(); 216 alertConnectionFailed(); 217 return; 218 } finally { 219 if (bin != null) { 220 try { 221 bin.close(); 222 } catch(IOException e){/* ignore */} 223 } 224 } 225 } 226 227 public boolean isCanceled() { 228 return canceled; 229 } 230 231 public boolean isSuccess() { 232 return success; 233 } 234 }