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.Utils; 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 HttpURLConnection 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 thrown if url is null. 046 */ 047 public ApiUrlTestTask(Component parent, String url) throws IllegalArgumentException { 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 147 /** 148 * Removes leading and trailing whitespace from the API URL and removes trailing '/'. 149 * 150 * @return the normalized API URL 151 */ 152 protected String getNormalizedApiUrl() { 153 String apiUrl = url.trim(); 154 while(apiUrl.endsWith("/")) { 155 apiUrl = apiUrl.substring(0, apiUrl.lastIndexOf('/')); 156 } 157 return apiUrl; 158 } 159 160 @Override 161 protected void realRun() throws SAXException, IOException, OsmTransferException { 162 try { 163 try { 164 new URL(getNormalizedApiUrl()); 165 } catch(MalformedURLException e) { 166 alertInvalidUrl(getNormalizedApiUrl()); 167 return; 168 } 169 URL capabilitiesUrl; 170 String getCapabilitiesUrl = getNormalizedApiUrl() + "/0.6/capabilities"; 171 try { 172 capabilitiesUrl = new URL(getCapabilitiesUrl); 173 } catch(MalformedURLException e) { 174 alertInvalidCapabilitiesUrl(getCapabilitiesUrl); 175 return; 176 } 177 178 synchronized(this) { 179 connection = Utils.openHttpConnection(capabilitiesUrl); 180 } 181 connection.setDoInput(true); 182 connection.setDoOutput(false); 183 connection.setRequestMethod("GET"); 184 connection.connect(); 185 186 if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { 187 alertInvalidServerResult(connection.getResponseCode()); 188 return; 189 } 190 191 try { 192 Capabilities.CapabilitiesParser.parse(new InputSource(connection.getInputStream())); 193 } catch (SAXException | ParserConfigurationException e) { 194 Main.warn(e.getMessage()); 195 alertInvalidCapabilities(); 196 return; 197 } 198 success = true; 199 } catch(IOException e) { 200 if (canceled) 201 // ignore exceptions 202 return; 203 Main.error(e); 204 alertConnectionFailed(); 205 return; 206 } 207 } 208 209 /** 210 * Determines if the test has been canceled. 211 * @return {@code true} if canceled, {@code false} otherwise 212 */ 213 public boolean isCanceled() { 214 return canceled; 215 } 216 217 /** 218 * Determines if the test has succeeded. 219 * @return {@code true} if success, {@code false} otherwise 220 */ 221 public boolean isSuccess() { 222 return success; 223 } 224}