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 }