/**
* TimeoutRequest
* Copyright 2010 by Michael Peter Christen, mc@yacy.net, Frankfurt a. M., Germany
* First released 08.10.2007 at http://yacy.net
*
* $LastChangedDate$
* $LastChangedRevision$
* $LastChangedBy$
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program in the file lgpl21.txt
* If not, see <http://www.gnu.org/licenses/>.
*/
package net.yacy.cora.protocol;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import net.yacy.cora.util.ConcurrentLog;
import net.yacy.kelondro.util.NamePrefixThreadFactory;
import jcifs.smb.SmbException;
import jcifs.smb.SmbFile;
/**
* TimeoutRequest is a class that can apply a timeout on method calls that may block
* for undefined time. Some network operations can only be accessed without a given
* time-out value. Using this class all network operations may be timed out.
* This class provides also some static methods that give already solutions for typical
* network operations that should be timed-out, like dns resolving and reverse domain name resolving.
*/
public class TimeoutRequest<E> {
public static boolean enable = true; // for tests
private final Callable<E> call;
/**
* initialize the TimeoutRequest with a callable method
*/
public TimeoutRequest(final Callable<E> call) {
this.call = call;
}
/**
* call the method using a time-out
* @param timeout
* @return
* @throws ExecutionException
*/
public E call(final long timeout) throws ExecutionException {
if (!enable) {try {
return this.call.call();
} catch (final Exception e1) {
throw new ExecutionException(e1);
}
}
final ExecutorService service = Executors
.newSingleThreadExecutor(new NamePrefixThreadFactory("TimeoutRequest"));
try {
final Future<E> taskFuture = service.submit(this.call);
final Runnable t = new Runnable() {
@Override
public void run() { taskFuture.cancel(true); }
};
service.execute(t);
service.shutdown();
try {
return taskFuture.get(timeout, TimeUnit.MILLISECONDS);
} catch (final CancellationException e) {
// callable was interrupted
throw new ExecutionException(e);
} catch (final InterruptedException e) {
// service was shutdown
throw new ExecutionException(e);
} catch (final ExecutionException e) {
// callable failed unexpectedly
throw e;
} catch (final TimeoutException e) {
// time-out
throw new ExecutionException(e);
}
} catch (final OutOfMemoryError e) {
ConcurrentLog.warn(TimeoutRequest.class.getName(), "OutOfMemoryError / retry follows", e);
// in case that no memory is there to create a new native thread
try {
return this.call.call();
} catch (final Exception e1) {
throw new ExecutionException(e1);
}
}
}
/**
* ping a remote server using a given uri and a time-out
* @param uri
* @param timeout
* @return true if the server exists and replies within the given time-out
*/
public static boolean ping(final String host, final int port, final int timeout) {
try {
return new TimeoutRequest<Boolean>(new Callable<Boolean>() {
@Override
public Boolean call() {
//long time = System.currentTimeMillis();
try {
final Socket socket = new Socket();
//System.out.println("PING socket create = " + (System.currentTimeMillis() - time) + " ms (" + host + ":" + port + ")"); time = System.currentTimeMillis();
socket.connect(new InetSocketAddress(host, port), timeout);
//System.out.println("PING socket connect = " + (System.currentTimeMillis() - time) + " ms (" + host + ":" + port + ")"); time = System.currentTimeMillis();
if (socket.isConnected()) {
socket.close();
return Boolean.TRUE;
}
//System.out.println("PING socket close = " + (System.currentTimeMillis() - time) + " ms (" + host + ":" + port + ")"); time = System.currentTimeMillis();
return Boolean.FALSE;
} catch (final UnknownHostException e) {
//System.out.println("PING socket UnknownHostException = " + (System.currentTimeMillis() - time) + " ms (" + host + ":" + port + ")"); time = System.currentTimeMillis();
return Boolean.FALSE;
} catch (final IOException e) {
//System.out.println("PING socket IOException = " + (System.currentTimeMillis() - time) + " ms (" + host + ":" + port + ")"); time = System.currentTimeMillis();
return Boolean.FALSE;
}
}
}).call(timeout).booleanValue();
} catch (ExecutionException ex) { // may happen on Timeout (see call)
return false;
}
}
/**
* perform a reverse domain name lookup for a given InetAddress within a given timeout
* @param i
* @param timeout
* @return the host name of a given InetAddress
* @throws ExecutionException
*/
public static String getHostName(final InetAddress i, final long timeout) throws ExecutionException {
return new TimeoutRequest<String>(new Callable<String>() {
@Override
public String call() { return i.getHostName(); }
}).call(timeout);
}
/**
* check if a smb file exists
* @param file
* @param timeout
* @return
* @throws IOException
*/
public static boolean exists(final SmbFile file, final long timeout) throws IOException {
try {
return new TimeoutRequest<Boolean>(new Callable<Boolean>() {
@Override
public Boolean call() { try {
return file.exists();
} catch (final SmbException e) {
return Boolean.FALSE;
} }
}).call(timeout).booleanValue();
} catch (final ExecutionException e) {
throw new IOException(e.getMessage());
}
}
/**
* check if a smb file can be read
* @param file
* @param timeout
* @return
* @throws IOException
*/
public static boolean canRead(final SmbFile file, final long timeout) throws IOException {
try {
return new TimeoutRequest<Boolean>(new Callable<Boolean>() {
@Override
public Boolean call() { try {
return file.canRead();
} catch (final SmbException e) {
return Boolean.FALSE;
} }
}).call(timeout).booleanValue();
} catch (final ExecutionException e) {
throw new IOException(e.getMessage());
}
}
/**
* check if a smb file ran be written
* @param file
* @param timeout
* @return
* @throws IOException
*/
public static boolean canWrite(final SmbFile file, final long timeout) throws IOException {
try {
return new TimeoutRequest<Boolean>(new Callable<Boolean>() {
@Override
public Boolean call() { try {
return file.canWrite();
} catch (final SmbException e) {
return Boolean.FALSE;
} }
}).call(timeout).booleanValue();
} catch (final ExecutionException e) {
throw new IOException(e.getMessage());
}
}
/**
* check if a smb file is hidden
* @param file
* @param timeout
* @return
* @throws IOException
*/
public static boolean isHidden(final SmbFile file, final long timeout) throws IOException {
try {
return new TimeoutRequest<Boolean>(new Callable<Boolean>() {
@Override
public Boolean call() { try {
return file.isHidden();
} catch (final SmbException e) {
return Boolean.FALSE;
} }
}).call(timeout).booleanValue();
} catch (final ExecutionException e) {
throw new IOException(e.getMessage());
}
}
/**
* check if a smb file is a directory
* @param file
* @param timeout
* @return
* @throws IOException
*/
public static boolean isDirectory(final SmbFile file, final long timeout) throws IOException {
try {
return new TimeoutRequest<Boolean>(new Callable<Boolean>() {
@Override
public Boolean call() { try {
return file.isDirectory();
} catch (final SmbException e) {
return Boolean.FALSE;
} }
}).call(timeout).booleanValue();
} catch (final ExecutionException e) {
throw new IOException(e.getMessage());
}
}
/**
* get the size of a smb file
* @param file
* @param timeout
* @return
* @throws IOException
*/
public static long length(final SmbFile file, final long timeout) throws IOException {
try {
return new TimeoutRequest<Long>(new Callable<Long>() {
@Override
public Long call() { try {
return file.length();
} catch (final SmbException e) {
return Long.valueOf(0);
} }
}).call(timeout).longValue();
} catch (final ExecutionException e) {
throw new IOException(file.toString() + ":" + e.getMessage());
}
}
/**
* get last-modified time of a smb file
* @param file
* @param timeout
* @return
* @throws IOException
*/
public static long lastModified(final SmbFile file, final long timeout) throws IOException {
try {
return new TimeoutRequest<Long>(new Callable<Long>() {
@Override
public Long call() { try {
return file.lastModified();
} catch (final SmbException e) {
return Long.valueOf(0);
} }
}).call(timeout).longValue();
} catch (final ExecutionException e) {
throw new IOException(e.getMessage());
}
}
/**
* get list of a smb directory
* @param file
* @param timeout
* @return
* @throws IOException
*/
public static String[] list(final SmbFile file, final long timeout) throws IOException {
try {
return new TimeoutRequest<String[]>(new Callable<String[]>() {
@Override
public String[] call() { try {
return file.list();
} catch (final SmbException e) {
//Log.logWarning("TimeoutRequest:list", file.toString() + " - no list", e);
return null;
} }
}).call(timeout);
} catch (final ExecutionException e) {
throw new IOException(e.getMessage());
}
}
}