// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.preferences.server;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Component;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.JOptionPane;
import org.openstreetmap.josm.data.Version;
import org.openstreetmap.josm.gui.HelpAwareOptionPane;
import org.openstreetmap.josm.gui.PleaseWaitRunnable;
import org.openstreetmap.josm.gui.help.HelpUtil;
import org.openstreetmap.josm.io.OsmTransferException;
import org.openstreetmap.josm.tools.CheckParameterUtil;
import org.xml.sax.SAXException;
/**
* This is an asynchronous task for testing whether an URL points to an OSM API server.
* It tries to retrieve a list of changesets from the given URL. If it succeeds, the method
* {@see #isSuccess()} replies true, otherwise false.
*
* Note: it fetches a list of changesets instead of the much smaller capabilities because - strangely enough -
* an OSM server "http://x.y.y/api/0.6" not only responds to "http://x.y.y/api/0.6/capabilities" but also
* to "http://x.y.y/api/0/capabilities" or "http://x.y.y/a/capabilities" with valid capabilities. If we get
* valid capabilities with an URL we therefore can't be sure that the base URL is valid API URL.
*
*/
public class ApiUrlTestTask extends PleaseWaitRunnable{
private String url;
private boolean canceled;
private boolean success;
private Component parent;
private HttpURLConnection connection;
/**
* Creates the task
*
* @param parent the parent component relative to which the {@see PleaseWaitRunnable}-Dialog is displayed
* @param url the url. Must not be null.
* @throws IllegalArgumentException thrown if url is null.
*/
public ApiUrlTestTask(Component parent, String url) throws IllegalArgumentException {
super(parent, tr("Testing OSM API URL ''{0}''", url), false /* don't ignore exceptions */);
CheckParameterUtil.ensureParameterNotNull(url,"url");
this.parent = parent;
this.url = url;
}
protected void alertInvalidUrl(String url) {
HelpAwareOptionPane.showOptionDialog(
parent,
tr("<html>"
+ "''{0}'' is not a valid OSM API URL.<br>"
+ "Please check the spelling and validate again."
+ "</html>",
url
),
tr("Invalid API URL"),
JOptionPane.ERROR_MESSAGE,
HelpUtil.ht("/Preferences/Connection#InvalidAPIUrl")
);
}
protected void alertInvalidChangesetUrl(String url) {
HelpAwareOptionPane.showOptionDialog(
parent,
tr("<html>"
+ "Failed to build URL ''{0}'' for validating the OSM API server.<br>"
+ "Please check the spelling of ''{1}'' and validate again."
+"</html>",
url,
getNormalizedApiUrl()
),
tr("Invalid API URL"),
JOptionPane.ERROR_MESSAGE,
HelpUtil.ht("/Preferences/Connection#InvalidAPIGetChangesetsUrl")
);
}
protected void alertConnectionFailed() {
HelpAwareOptionPane.showOptionDialog(
parent,
tr("<html>"
+ "Failed to connect to the URL ''{0}''.<br>"
+ "Please check the spelling of ''{1}'' and your Internet connection and validate again."
+"</html>",
url,
getNormalizedApiUrl()
),
tr("Connection to API failed"),
JOptionPane.ERROR_MESSAGE,
HelpUtil.ht("/Preferences/Connection#ConnectionToAPIFailed")
);
}
protected void alertInvalidServerResult(int retCode) {
HelpAwareOptionPane.showOptionDialog(
parent,
tr("<html>"
+ "Failed to retrieve a list of changesets from the OSM API server at<br>"
+ "''{1}''. The server responded with the return code {0} instead of 200.<br>"
+ "Please check the spelling of ''{1}'' and validate again."
+ "</html>",
retCode,
getNormalizedApiUrl()
),
tr("Connection to API failed"),
JOptionPane.ERROR_MESSAGE,
HelpUtil.ht("/Preferences/Connection#InvalidServerResult")
);
}
protected void alertInvalidChangesetList() {
HelpAwareOptionPane.showOptionDialog(
parent,
tr("<html>"
+ "The OSM API server at ''{0}'' did not return a valid response.<br>"
+ "It is likely that ''{0}'' is not an OSM API server.<br>"
+ "Please check the spelling of ''{0}'' and validate again."
+ "</html>",
getNormalizedApiUrl()
),
tr("Connection to API failed"),
JOptionPane.ERROR_MESSAGE,
HelpUtil.ht("/Preferences/Connection#InvalidSettings")
);
}
@Override
protected void cancel() {
canceled = true;
synchronized(this) {
if (connection != null) {
connection.disconnect();
}
}
}
@Override
protected void finish() {}
/**
* Removes leading and trailing whitespace from the API URL and removes trailing
* '/'.
*
* @return the normalized API URL
*/
protected String getNormalizedApiUrl() {
String apiUrl = url.trim();
while(apiUrl.endsWith("/")) {
apiUrl = apiUrl.substring(0, apiUrl.lastIndexOf("/"));
}
return apiUrl;
}
@Override
protected void realRun() throws SAXException, IOException, OsmTransferException {
BufferedReader bin = null;
try {
try {
new URL(getNormalizedApiUrl());
} catch(MalformedURLException e) {
alertInvalidUrl(getNormalizedApiUrl());
return;
}
URL capabilitiesUrl;
String getChangesetsUrl = getNormalizedApiUrl() + "/0.6/changesets";
try {
capabilitiesUrl = new URL(getChangesetsUrl);
} catch(MalformedURLException e) {
alertInvalidChangesetUrl(getChangesetsUrl);
return;
}
synchronized(this) {
connection = (HttpURLConnection)capabilitiesUrl.openConnection();
}
connection.setDoInput(true);
connection.setDoOutput(false);
connection.setRequestMethod("GET");
connection.setRequestProperty("User-Agent", Version.getInstance().getAgentString());
connection.setRequestProperty("Host", connection.getURL().getHost());
connection.connect();
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
alertInvalidServerResult(connection.getResponseCode());
return;
}
StringBuilder changesets = new StringBuilder();
bin = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = bin.readLine()) != null) {
changesets.append(line).append("\n");
}
if (! (changesets.toString().contains("<osm") && changesets.toString().contains("</osm>"))) {
// heuristic: if there isn't an opening and closing "<osm>" tag in the returned content,
// then we didn't get a list of changesets in return. Could be replaced by explicitly parsing
// the result but currently not worth the effort.
alertInvalidChangesetList();
return;
}
success = true;
} catch(IOException e) {
if (canceled)
// ignore exceptions
return;
e.printStackTrace();
alertConnectionFailed();
return;
} finally {
if (bin != null) {
try {
bin.close();
} catch(IOException e){/* ignore */}
}
}
}
public boolean isCanceled() {
return canceled;
}
public boolean isSuccess() {
return success;
}
}