blob: 7838392918c92cf7564d2aacab98afd7a2f05e61 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2007 Mylyn project committers and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.mylyn.web.core;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.UnknownHostException;
import java.net.Proxy.Type;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NTCredentials;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.params.DefaultHttpParams;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.eclipse.core.net.proxy.IProxyData;
import org.eclipse.core.net.proxy.IProxyService;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.mylyn.internal.web.core.CloneableHostConfiguration;
import org.eclipse.mylyn.internal.web.core.PollingProtocolSocketFactory;
import org.eclipse.mylyn.internal.web.core.PollingSslProtocolSocketFactory;
/**
* @author Mik Kersten
* @author Steffen Pingel
* @author Leo Dos Santos - getFaviconForUrl
* @author Rob Elves
*
* API-3.0 rename to WebUtil
*/
public class WebClientUtil {
private static final int MAX_THREADS = 10;
/**
* like Mylyn/2.1.0 (Rally Connector 1.0) Eclipse/3.3.0 (JBuilder 2007) HttpClient/3.0.1 Java/1.5.0_11 (Sun)
* Linux/2.6.20-16-lowlatency (i386; en)
*/
public static final String USER_AGENT;
public static final String CONTENT_ENCODING_GZIP = "gzip";
public static final int CONNNECT_TIMEOUT = 60000;
public static final int SOCKET_TIMEOUT = 60000;
private static final int HTTP_PORT = 80;
private static final int HTTPS_PORT = 443;
private static final int SOCKS_PORT = 1080;
private static final long POLL_INTERVAL = 1000;
private static OutputStream logOutputStream = System.err;
private static boolean loggingEnabled = false;
private static final String USER_AGENT_PREFIX;
private static final String USER_AGENT_POSTFIX;
private static final ExecutorService service = new ThreadPoolExecutor(1, MAX_THREADS, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
private static String stripQualifier(String longVersion) {
if (longVersion == null) {
return "";
}
String parts[] = longVersion.split("\\.");
StringBuilder version = new StringBuilder();
if (parts.length > 0) {
version.append("/");
version.append(parts[0]);
if (parts.length > 1) {
version.append(".");
version.append(parts[1]);
if (parts.length > 2) {
version.append(".");
version.append(parts[2]);
}
}
}
return version.toString();
}
private static String getBundleVersion(Plugin plugin) {
if (null == plugin) {
return "";
}
Object bundleVersion = plugin.getBundle().getHeaders().get("Bundle-Version");
if (null == bundleVersion) {
return "";
}
return stripQualifier((String) bundleVersion);
}
static {
initCommonsLoggingSettings();
StringBuilder sb = new StringBuilder();
sb.append("Mylyn");
sb.append(getBundleVersion(WebCorePlugin.getDefault()));
USER_AGENT_PREFIX = sb.toString();
sb.setLength(0);
if (System.getProperty("org.osgi.framework.vendor") != null) {
sb.append(" ");
sb.append(System.getProperty("org.osgi.framework.vendor"));
sb.append(stripQualifier(System.getProperty("osgi.framework.version")));
if (System.getProperty("eclipse.product") != null) {
sb.append(" (");
sb.append(System.getProperty("eclipse.product"));
sb.append(")");
}
}
sb.append(" ");
sb.append(DefaultHttpParams.getDefaultParams().getParameter(HttpMethodParams.USER_AGENT).toString().split("-")[1]);
sb.append(" Java/");
sb.append(System.getProperty("java.version"));
sb.append(" (");
sb.append(System.getProperty("java.vendor").split(" ")[0]);
sb.append(") ");
sb.append(System.getProperty("os.name"));
sb.append("/");
sb.append(System.getProperty("os.version"));
sb.append(" (");
sb.append(System.getProperty("os.arch"));
if (System.getProperty("osgi.nl") != null) {
sb.append("; ");
sb.append(System.getProperty("osgi.nl"));
}
sb.append(")");
USER_AGENT_POSTFIX = sb.toString();
USER_AGENT = USER_AGENT_PREFIX + USER_AGENT_POSTFIX;
}
public static void initCommonsLoggingSettings() {
// Remove?
System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
System.setProperty("org.apache.commons.logging.simplelog.defaultlog", "off");
System.setProperty("org.apache.commons.logging.simplelog.log.httpclient.wire.header", "off");
System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient", "off");
// FIXME this does not work with the commons logging Orbit bundle which does not see the WebClientLog class
// Update our assigned logger to use custom WebClientLog
// LogFactory logFactory = LogFactory.getFactory();
// logFactory.setAttribute("org.apache.commons.logging.Log", "org.eclipse.mylyn.web.core.WebClientLog");
// Note: level being set by Web
// logFactory.setAttribute("org.apache.commons.logging.simplelog.showdatetime",
// "true");
// logFactory.setAttribute("org.apache.commons.logging.simplelog.log.httpclient.wire",
// "debug");
// logFactory.setAttribute("org.apache.commons.logging.simplelog.log.httpclient.wire.header",
// "debug");
// logFactory.setAttribute("org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient",
// "debug");
// logFactory.setAttribute(
// "org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient.HttpConnection",
// "trace");
// logFactory.release();
}
public static OutputStream getLogStream() {
return logOutputStream;
}
public static void setLogStream(OutputStream stream) {
logOutputStream = stream;
}
public static void setLoggingEnabled(boolean enabled) {
loggingEnabled = enabled;
}
static boolean isRepositoryHttps(String repositoryUrl) {
return repositoryUrl.matches("https.*");
}
public static int getPort(String repositoryUrl) {
int colonSlashSlash = repositoryUrl.indexOf("://");
int firstSlash = repositoryUrl.indexOf("/", colonSlashSlash + 3);
int colonPort = repositoryUrl.indexOf(':', colonSlashSlash + 1);
if (firstSlash == -1) {
firstSlash = repositoryUrl.length();
}
if (colonPort < 0 || colonPort > firstSlash) {
return isRepositoryHttps(repositoryUrl) ? HTTPS_PORT : HTTP_PORT;
}
int requestPath = repositoryUrl.indexOf('/', colonPort + 1);
int end = requestPath < 0 ? repositoryUrl.length() : requestPath;
String port = repositoryUrl.substring(colonPort + 1, end);
if (port.length() == 0) {
return isRepositoryHttps(repositoryUrl) ? HTTPS_PORT : HTTP_PORT;
}
return Integer.parseInt(port);
}
// API-3.0 rename to getHost
public static String getDomain(String repositoryUrl) {
String result = repositoryUrl;
int colonSlashSlash = repositoryUrl.indexOf("://");
if (colonSlashSlash >= 0) {
result = repositoryUrl.substring(colonSlashSlash + 3);
}
int colonPort = result.indexOf(':');
int requestPath = result.indexOf('/');
int substringEnd;
// minimum positive, or string length
if (colonPort > 0 && requestPath > 0) {
substringEnd = Math.min(colonPort, requestPath);
} else if (colonPort > 0) {
substringEnd = colonPort;
} else if (requestPath > 0) {
substringEnd = requestPath;
} else {
substringEnd = result.length();
}
return result.substring(0, substringEnd);
}
public static String getRequestPath(String repositoryUrl) {
int colonSlashSlash = repositoryUrl.indexOf("://");
int requestPath = repositoryUrl.indexOf('/', colonSlashSlash + 3);
if (requestPath < 0) {
return "";
} else {
return repositoryUrl.substring(requestPath);
}
}
/**
* @deprecated use {@link #createHostConfiguration(HttpClient, String, AbstractWebLocation, IProgressMonitor)}
* instead
*/
@Deprecated
public static void setupHttpClient(HttpClient client, Proxy proxySettings, String repositoryUrl, String user,
String password) {
setupHttpClientParams(client, null);
if (proxySettings != null && !Proxy.NO_PROXY.equals(proxySettings)
/* && !WebClientUtil.repositoryUsesHttps(repositoryUrl) */
&& proxySettings.address() instanceof InetSocketAddress) {
InetSocketAddress address = (InetSocketAddress) proxySettings.address();
client.getHostConfiguration().setProxy(WebClientUtil.getDomain(address.getHostName()), address.getPort());
if (proxySettings instanceof AuthenticatedProxy) {
AuthenticatedProxy authProxy = (AuthenticatedProxy) proxySettings;
Credentials credentials = getCredentials(authProxy.getUserName(), authProxy.getPassword(),
address.getAddress());
AuthScope proxyAuthScope = new AuthScope(address.getHostName(), address.getPort(), AuthScope.ANY_REALM);
client.getState().setProxyCredentials(proxyAuthScope, credentials);
}
}
if (user != null && password != null) {
AuthScope authScope = new AuthScope(WebClientUtil.getDomain(repositoryUrl),
WebClientUtil.getPort(repositoryUrl), AuthScope.ANY_REALM);
try {
client.getState().setCredentials(authScope, getCredentials(user, password, InetAddress.getLocalHost()));
} catch (UnknownHostException e) {
client.getState().setCredentials(authScope, getCredentials(user, password, null));
}
}
if (WebClientUtil.isRepositoryHttps(repositoryUrl)) {
Protocol acceptAllSsl = new Protocol("https",
(ProtocolSocketFactory) SslProtocolSocketFactory.getInstance(),
WebClientUtil.getPort(repositoryUrl));
client.getHostConfiguration().setHost(WebClientUtil.getDomain(repositoryUrl),
WebClientUtil.getPort(repositoryUrl), acceptAllSsl);
Protocol.registerProtocol("https", acceptAllSsl);
} else {
client.getHostConfiguration().setHost(WebClientUtil.getDomain(repositoryUrl),
WebClientUtil.getPort(repositoryUrl));
}
}
public static Credentials getCredentials(AuthenticatedProxy authProxy, InetSocketAddress address) {
return getCredentials(authProxy.getUserName(), authProxy.getPassword(), address.getAddress());
}
private static Credentials getCredentials(final String username, final String password, final InetAddress address) {
int i = username.indexOf("\\");
if (i > 0 && i < username.length() - 1 && address != null) {
return new NTCredentials(username.substring(i + 1), password, address.getHostName(), username.substring(0,
i));
} else {
return new UsernamePasswordCredentials(username, password);
}
}
/**
* @since 2.2
*/
public static Credentials getHttpClientCredentials(AuthenticationCredentials credentials, String host) {
String username = credentials.getUserName();
String password = credentials.getPassword();
int i = username.indexOf("\\");
if (i > 0 && i < username.length() - 1 && host != null) {
return new NTCredentials(username.substring(i + 1), password, host, username.substring(0, i));
} else {
return new UsernamePasswordCredentials(username, password);
}
}
/** utility method, should use TaskRepository.getProxy() */
public static Proxy getProxy(String proxyHost, String proxyPort, String proxyUsername, String proxyPassword) {
boolean authenticated = (proxyUsername != null && proxyPassword != null && proxyUsername.length() > 0 && proxyPassword.length() > 0);
if (proxyHost != null && proxyHost.length() > 0 && proxyPort != null && proxyPort.length() > 0) {
int proxyPortNum = Integer.parseInt(proxyPort);
InetSocketAddress sockAddr = new InetSocketAddress(proxyHost, proxyPortNum);
if (authenticated) {
return new AuthenticatedProxy(Type.HTTP, sockAddr, proxyUsername, proxyPassword);
} else {
return new Proxy(Type.HTTP, sockAddr);
}
}
return Proxy.NO_PROXY;
}
/**
* utility method, proxy should be obtained via TaskRepository.getProxy()
*
* TODO: deprecate
*
* @return proxy as defined in platform proxy settings property page, Proxy.NO_PROXY otherwise
*/
public static Proxy getPlatformProxy() {
Proxy proxy = Proxy.NO_PROXY;
IProxyService service = WebCorePlugin.getProxyService();
if (service != null && service.isProxiesEnabled()) {
IProxyData data = service.getProxyData(IProxyData.HTTP_PROXY_TYPE);
if (data.getHost() != null) {
String proxyHost = data.getHost();
int proxyPort = data.getPort();
// Change the IProxyData default port to the Java default port
if (proxyPort == -1) {
proxyPort = 0;
}
InetSocketAddress sockAddr = new InetSocketAddress(proxyHost, proxyPort);
proxy = new Proxy(Type.HTTP, sockAddr);
}
}
return proxy;
}
/**
* utility method, proxy should be obtained via TaskRepository.getProxy()
*
* @return proxy as defined in platform proxy settings property page, Proxy.NO_PROXY otherwise
* @since 2.1
*/
public static Proxy getPlatformProxy(String url) {
Proxy proxy = Proxy.NO_PROXY;
Type proxyType = Type.DIRECT;
IProxyService service = WebCorePlugin.getProxyService();
if (service != null && service.isProxiesEnabled()) {
IProxyData proxyDataInUse = null;
IProxyData httpProxy = service.getProxyDataForHost(getDomain(url), IProxyData.HTTP_PROXY_TYPE);
IProxyData httpsProxy = service.getProxyDataForHost(getDomain(url), IProxyData.HTTPS_PROXY_TYPE);
// See TODO below regarding socks
// IProxyData socksProxy =
// service.getProxyDataForHost(getDomain(url),
// IProxyData.SOCKS_PROXY_TYPE);
if (url.startsWith("https")) {
if (httpsProxy != null) {
proxyDataInUse = httpsProxy;
} else if (httpProxy != null) {
proxyDataInUse = httpProxy;
}
} else if (url.startsWith("http")) {
if (httpProxy != null) {
proxyDataInUse = httpProxy;
}
}
// TODO: Support for SOCKS handled by Eclipse platform
// currently hosts in exception list may not be excluded so will
// require custom socket construction in httpclient.
// else {
// if (socksProxy != null) {
// proxyDataInUse = socksProxy;
// }
// if (httpsProxy != null) {
// proxyDataInUse = httpsProxy;
// } else if (httpProxy != null) {
// proxyDataInUse = httpProxy;
// }
// }
if (proxyDataInUse != null) {
int proxyPort = proxyDataInUse.getPort();
if (proxyDataInUse.getType().equals(IProxyData.HTTP_PROXY_TYPE)) {
proxyType = Type.HTTP;
if (proxyPort == -1) {
proxyPort = HTTP_PORT;
}
} else if (proxyDataInUse.getType().equals(IProxyData.HTTPS_PROXY_TYPE)) {
proxyType = Type.HTTP;
if (proxyPort == -1) {
proxyPort = HTTPS_PORT;
}
} else {
proxyType = Type.SOCKS;
if (proxyPort == -1) {
proxyPort = SOCKS_PORT;
}
}
String proxyHost = proxyDataInUse.getHost();
String proxyUserName = proxyDataInUse.getUserId();
String proxyPassword = proxyDataInUse.getPassword();
// Change the IProxyData default port to the Java default port
// if (proxyPort == -1)
// proxyPort = 0;
InetSocketAddress sockAddr = new InetSocketAddress(proxyHost, proxyPort);
if (proxyUserName != null && proxyUserName.length() > 0 && proxyPassword != null
&& proxyPassword.length() > 0) {
proxy = new AuthenticatedProxy(proxyType, sockAddr, proxyUserName, proxyPassword);
} else {
proxy = new Proxy(proxyType, sockAddr);
}
}
}
return proxy;
}
/**
* @since 2.2
*/
public static boolean isLoggingEnabled() {
return loggingEnabled;
}
/**
* @deprecated use {@link #createHostConfiguration(HttpClient, String, AbstractWebLocation, IProgressMonitor)}
* instead
*/
@Deprecated
public static void setupHttpClient(HttpClient client, String userAgent, AbstractWebLocation location) {
if (client == null || location == null) {
throw new IllegalArgumentException();
}
String url = location.getUrl();
String host = WebClientUtil.getDomain(url);
int port = WebClientUtil.getPort(url);
setupHttpClientParams(client, userAgent);
setupHttpClientProxy(client, client.getHostConfiguration(), location);
AuthenticationCredentials credentials = location.getCredentials(AuthenticationType.HTTP);
if (credentials != null) {
AuthScope authScope = new AuthScope(host, port, AuthScope.ANY_REALM);
client.getState().setCredentials(authScope, getHttpClientCredentials(credentials, host));
}
if (WebClientUtil.isRepositoryHttps(url)) {
Protocol acceptAllSsl = new Protocol("https",
(ProtocolSocketFactory) SslProtocolSocketFactory.getInstance(), port);
client.getHostConfiguration().setHost(host, port, acceptAllSsl);
// globally register handler, unfortunately Axis requires this
Protocol.registerProtocol("https", acceptAllSsl);
} else {
client.getHostConfiguration().setHost(host, port);
}
}
/**
* @since 2.3
*/
public static HostConfiguration createHostConfiguration(HttpClient client, String userAgent,
AbstractWebLocation location, IProgressMonitor monitor) {
if (client == null || location == null) {
throw new IllegalArgumentException();
}
String url = location.getUrl();
String host = WebClientUtil.getDomain(url);
int port = WebClientUtil.getPort(url);
HostConfiguration hostConfiguration = new CloneableHostConfiguration();
setupHttpClientParams(client, userAgent);
setupHttpClientProxy(client, hostConfiguration, location);
AuthenticationCredentials credentials = location.getCredentials(AuthenticationType.HTTP);
if (credentials != null) {
AuthScope authScope = new AuthScope(host, port, AuthScope.ANY_REALM);
client.getState().setCredentials(authScope, getHttpClientCredentials(credentials, host));
}
if (WebClientUtil.isRepositoryHttps(url)) {
ProtocolSocketFactory socketFactory = new PollingSslProtocolSocketFactory(monitor);
Protocol protocol = new Protocol("https", socketFactory, port);
hostConfiguration.setHost(host, port, protocol);
} else {
ProtocolSocketFactory socketFactory = new PollingProtocolSocketFactory(monitor);
Protocol protocol = new Protocol("http", socketFactory, port);
hostConfiguration.setHost(host, port, protocol);
}
return hostConfiguration;
}
private static void setupHttpClientProxy(HttpClient client, HostConfiguration hostConfiguration,
AbstractWebLocation location) {
String host = WebClientUtil.getDomain(location.getUrl());
Proxy proxy;
if (WebClientUtil.isRepositoryHttps(location.getUrl())) {
proxy = location.getProxyForHost(host, IProxyData.HTTP_PROXY_TYPE);
} else {
proxy = location.getProxyForHost(host, IProxyData.HTTPS_PROXY_TYPE);
}
if (proxy != null && !Proxy.NO_PROXY.equals(proxy)) {
InetSocketAddress address = (InetSocketAddress) proxy.address();
hostConfiguration.setProxy(address.getHostName(), address.getPort());
if (proxy instanceof AuthenticatedProxy) {
AuthenticatedProxy authProxy = (AuthenticatedProxy) proxy;
Credentials credentials = getCredentials(authProxy.getUserName(), authProxy.getPassword(),
address.getAddress());
AuthScope proxyAuthScope = new AuthScope(address.getHostName(), address.getPort(), AuthScope.ANY_REALM);
client.getState().setProxyCredentials(proxyAuthScope, credentials);
}
} else {
hostConfiguration.setProxyHost(null);
}
}
private static void setupHttpClientParams(HttpClient client, String userAgent) {
client.getParams().setBooleanParameter(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, true);
client.getParams().setParameter(HttpMethodParams.USER_AGENT, getUserAgent(userAgent));
client.getHttpConnectionManager().getParams().setSoTimeout(WebClientUtil.SOCKET_TIMEOUT);
client.getHttpConnectionManager().getParams().setConnectionTimeout(WebClientUtil.CONNNECT_TIMEOUT);
}
/**
* Returns a user agent string that contains information about the platform and operating system. The
* <code>product</code> parameter allows to additional specify custom text that is inserted into the returned
* string. The exact return value depends on the environment.
*
* <p>
* Examples:
* <ul>
* <li>Headless: <code>Mylyn MyProduct HttpClient/3.1 Java/1.5.0_13 (Sun) Linux/2.6.22-14-generic (i386)</code>
* <li>Eclipse:
* <code>Mylyn/2.2.0 Eclipse/3.4.0 (org.eclipse.sdk.ide) HttpClient/3.1 Java/1.5.0_13 (Sun) Linux/2.6.22-14-generic (i386; en_CA)</code>
*
* @param product
* an identifier that is inserted into the returned user agent string
* @return a user agent string
* @since 2.3
*/
public static String getUserAgent(String product) {
if (product != null && product.length() > 0) {
StringBuilder sb = new StringBuilder();
sb.append(USER_AGENT_PREFIX);
sb.append(" ");
sb.append(product);
sb.append(USER_AGENT_POSTFIX);
return sb.toString();
} else {
return USER_AGENT;
}
}
/**
* @since 3.0
*/
public static int execute(final HttpClient client, final HostConfiguration hostConfiguration,
final HttpMethod method, IProgressMonitor monitor) throws IOException {
if (client == null || method == null) {
throw new IllegalArgumentException();
}
monitor = Policy.monitorFor(monitor);
AbortableCallable<Integer> executor = new AbortableCallable<Integer>() {
public Integer call() throws Exception {
return client.executeMethod(hostConfiguration, method);
}
public void abort() {
method.abort();
}
};
return pollIo(monitor, executor);
}
/**
* @since 3.0
*/
public static void connect(final Socket socket, final InetSocketAddress address, final int timeout,
IProgressMonitor monitor) throws IOException {
if (socket == null) {
throw new IllegalArgumentException();
}
AbortableCallable<?> executor = new AbortableCallable<Object>() {
public Object call() throws Exception {
socket.connect(address, timeout);
return null;
}
public void abort() {
try {
socket.close();
} catch (IOException e) {
// ignore
}
}
};
pollIo(monitor, executor);
}
@SuppressWarnings("unchecked")
private static <T> T pollIo(IProgressMonitor monitor, AbortableCallable<?> executor) throws IOException {
try {
return (T) poll(monitor, executor);
} catch (IOException e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* @since 3.0
*/
public static <T> T poll(IProgressMonitor monitor, AbortableCallable<T> executor) throws Throwable {
monitor = Policy.monitorFor(monitor);
Future<T> future = service.submit(executor);
while (true) {
if (monitor.isCanceled()) {
if (!future.cancel(false)) {
executor.abort();
}
// wait for executor to finish
try {
if (!future.isCancelled()) {
future.get();
}
} catch (InterruptedException e) {
// ignore
} catch (ExecutionException e) {
// ignore
}
throw new OperationCanceledException();
}
try {
return future.get(POLL_INTERVAL, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new InterruptedIOException();
} catch (ExecutionException e) {
throw e.getCause();
} catch (TimeoutException ignored) {
}
}
}
/**
* @since 3.0
*/
public interface AbortableCallable<T> extends Callable<T> {
public void abort();
}
}