001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.preferences.server; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Component; 007import java.io.IOException; 008import java.net.HttpURLConnection; 009import java.net.MalformedURLException; 010import java.net.URL; 011 012import javax.swing.JOptionPane; 013import javax.xml.parsers.ParserConfigurationException; 014 015import org.openstreetmap.josm.Main; 016import org.openstreetmap.josm.gui.HelpAwareOptionPane; 017import org.openstreetmap.josm.gui.PleaseWaitRunnable; 018import org.openstreetmap.josm.gui.help.HelpUtil; 019import org.openstreetmap.josm.io.Capabilities; 020import org.openstreetmap.josm.io.OsmTransferException; 021import org.openstreetmap.josm.tools.CheckParameterUtil; 022import org.openstreetmap.josm.tools.HttpClient; 023import org.xml.sax.InputSource; 024import org.xml.sax.SAXException; 025 026/** 027 * This is an asynchronous task for testing whether an URL points to an OSM API server. 028 * It tries to retrieve capabilities from the given URL. If it succeeds, the method 029 * {@link #isSuccess()} replies true, otherwise false. 030 * @since 2745 031 */ 032public class ApiUrlTestTask extends PleaseWaitRunnable { 033 034 private final String url; 035 private boolean canceled; 036 private boolean success; 037 private final Component parent; 038 private HttpClient connection; 039 040 /** 041 * Constructs a new {@code ApiUrlTestTask}. 042 * 043 * @param parent the parent component relative to which the {@link PleaseWaitRunnable}-Dialog is displayed 044 * @param url the url. Must not be null. 045 * @throws IllegalArgumentException if url is null. 046 */ 047 public ApiUrlTestTask(Component parent, String url) { 048 super(parent, tr("Testing OSM API URL ''{0}''", url), false /* don't ignore exceptions */); 049 CheckParameterUtil.ensureParameterNotNull(url, "url"); 050 this.parent = parent; 051 this.url = url; 052 } 053 054 protected void alertInvalidUrl(String url) { 055 HelpAwareOptionPane.showMessageDialogInEDT( 056 parent, 057 tr("<html>" 058 + "''{0}'' is not a valid OSM API URL.<br>" 059 + "Please check the spelling and validate again." 060 + "</html>", 061 url 062 ), 063 tr("Invalid API URL"), 064 JOptionPane.ERROR_MESSAGE, 065 HelpUtil.ht("/Preferences/Connection#InvalidAPIUrl") 066 ); 067 } 068 069 protected void alertInvalidCapabilitiesUrl(String url) { 070 HelpAwareOptionPane.showMessageDialogInEDT( 071 parent, 072 tr("<html>" 073 + "Failed to build URL ''{0}'' for validating the OSM API server.<br>" 074 + "Please check the spelling of ''{1}'' and validate again." 075 +"</html>", 076 url, 077 getNormalizedApiUrl() 078 ), 079 tr("Invalid API URL"), 080 JOptionPane.ERROR_MESSAGE, 081 HelpUtil.ht("/Preferences/Connection#InvalidAPIGetChangesetsUrl") 082 ); 083 } 084 085 protected void alertConnectionFailed() { 086 HelpAwareOptionPane.showMessageDialogInEDT( 087 parent, 088 tr("<html>" 089 + "Failed to connect to the URL ''{0}''.<br>" 090 + "Please check the spelling of ''{1}'' and your Internet connection and validate again." 091 +"</html>", 092 url, 093 getNormalizedApiUrl() 094 ), 095 tr("Connection to API failed"), 096 JOptionPane.ERROR_MESSAGE, 097 HelpUtil.ht("/Preferences/Connection#ConnectionToAPIFailed") 098 ); 099 } 100 101 protected void alertInvalidServerResult(int retCode) { 102 HelpAwareOptionPane.showMessageDialogInEDT( 103 parent, 104 tr("<html>" 105 + "Failed to retrieve a list of changesets from the OSM API server at<br>" 106 + "''{1}''. The server responded with the return code {0} instead of 200.<br>" 107 + "Please check the spelling of ''{1}'' and validate again." 108 + "</html>", 109 retCode, 110 getNormalizedApiUrl() 111 ), 112 tr("Connection to API failed"), 113 JOptionPane.ERROR_MESSAGE, 114 HelpUtil.ht("/Preferences/Connection#InvalidServerResult") 115 ); 116 } 117 118 protected void alertInvalidCapabilities() { 119 HelpAwareOptionPane.showMessageDialogInEDT( 120 parent, 121 tr("<html>" 122 + "The OSM API server at ''{0}'' did not return a valid response.<br>" 123 + "It is likely that ''{0}'' is not an OSM API server.<br>" 124 + "Please check the spelling of ''{0}'' and validate again." 125 + "</html>", 126 getNormalizedApiUrl() 127 ), 128 tr("Connection to API failed"), 129 JOptionPane.ERROR_MESSAGE, 130 HelpUtil.ht("/Preferences/Connection#InvalidSettings") 131 ); 132 } 133 134 @Override 135 protected void cancel() { 136 canceled = true; 137 synchronized (this) { 138 if (connection != null) { 139 connection.disconnect(); 140 } 141 } 142 } 143 144 @Override 145 protected void finish() { 146 // Do nothing 147 } 148 149 /** 150 * Removes leading and trailing whitespace from the API URL and removes trailing '/'. 151 * 152 * @return the normalized API URL 153 */ 154 protected String getNormalizedApiUrl() { 155 String apiUrl = url.trim(); 156 while (apiUrl.endsWith("/")) { 157 apiUrl = apiUrl.substring(0, apiUrl.lastIndexOf('/')); 158 } 159 return apiUrl; 160 } 161 162 @Override 163 protected void realRun() throws SAXException, IOException, OsmTransferException { 164 try { 165 try { 166 new URL(getNormalizedApiUrl()); 167 } catch (MalformedURLException e) { 168 alertInvalidUrl(getNormalizedApiUrl()); 169 return; 170 } 171 URL capabilitiesUrl; 172 String getCapabilitiesUrl = getNormalizedApiUrl() + "/0.6/capabilities"; 173 try { 174 capabilitiesUrl = new URL(getCapabilitiesUrl); 175 } catch (MalformedURLException e) { 176 alertInvalidCapabilitiesUrl(getCapabilitiesUrl); 177 return; 178 } 179 180 synchronized (this) { 181 connection = HttpClient.create(capabilitiesUrl); 182 connection.connect(); 183 } 184 185 if (connection.getResponse().getResponseCode() != HttpURLConnection.HTTP_OK) { 186 alertInvalidServerResult(connection.getResponse().getResponseCode()); 187 return; 188 } 189 190 try { 191 Capabilities.CapabilitiesParser.parse(new InputSource(connection.getResponse().getContent())); 192 } catch (SAXException | ParserConfigurationException e) { 193 Main.warn(e); 194 alertInvalidCapabilities(); 195 return; 196 } 197 success = true; 198 } catch (IOException e) { 199 if (canceled) 200 // ignore exceptions 201 return; 202 Main.error(e); 203 alertConnectionFailed(); 204 return; 205 } 206 } 207 208 /** 209 * Determines if the test has been canceled. 210 * @return {@code true} if canceled, {@code false} otherwise 211 */ 212 public boolean isCanceled() { 213 return canceled; 214 } 215 216 /** 217 * Determines if the test has succeeded. 218 * @return {@code true} if success, {@code false} otherwise 219 */ 220 public boolean isSuccess() { 221 return success; 222 } 223}