All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xins.server.CheckLinks Maven / Gradle / Ivy

There is a newer version: 3.0
Show newest version
/*
 * $Id: CheckLinks.java,v 1.37 2007/05/22 11:13:26 agoubard Exp $
 *
 * Copyright 2003-2007 Orange Nederland Breedband B.V.
 * See the COPYRIGHT file for redistribution and use restrictions.
 */
package org.xins.server;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpRecoverableException;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.httpclient.methods.OptionsMethod;

import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.Utils;
import org.xins.common.service.Descriptor;
import org.xins.common.service.TargetDescriptor;
import org.xins.common.xml.ElementBuilder;

/**
 * Checks all the links in the given descriptors list and builds
 * a FunctionResult. It connects to each link in
 * {@link TargetDescriptor}s in {@link Descriptor}s list using a
 * {@link URLChecker} and calculates the total links count and
 * total links failures. The returned {@link FunctionResult} contains
 * information about total links checked, failures and details.
 *
 * The following example uses a {@link CheckLinks} object to get the
 * {@link FunctionResult}.
 *
 * 
 * FunctionResult result = CheckLinks.checkLinks(descriptorList);
 *
 * // Returns parameters
 * result.getParameters();
 * 
* * @version $Revision: 1.37 $ $Date: 2007/05/22 11:13:26 $ * @author Tauseef Rehman */ class CheckLinks { /** * The failure message to be added in the FunctionResult when * the exception is UnknownHostException. */ private static final String UNKNOWN_HOST = "UnknownHost"; /** * The failure message to be added in the FunctionResult when * the exception is ConnectTimeoutException or the message * of the exception starts with "Connect timed out". */ private static final String CONNECTION_TIMEOUT = "ConnectionTimeout"; /** * The failure message to be added in the FunctionResult when * the exception is ConnectException. */ private static final String CONNECTION_REFUSAL = "ConnectionRefusal"; /** * The failure message to be added in the FunctionResult when * the exception is SocketTimeoutException. */ private static final String SOCKET_TIMEOUT = "SocketTimeout"; /** * The failure message to be added in the FunctionResult when * the exception is IOException. */ private static final String OTHER_IO_ERROR = "OtherIOError"; /** * The failure message to be added in the FunctionResult when * the exception is an unknown Exception. */ private static final String OTHER_FAILURE = "OtherFailure"; /** * The success message to be added in the FunctionResult. */ private static final String SUCCESS = "Success"; /** * HTTP retry handler that does not allow any retries. */ private static DefaultHttpMethodRetryHandler NO_RETRIES = new DefaultHttpMethodRetryHandler(0, false); /** * Checks all the links in TargetDescriptors inside the * Descriptor list and builds a FunctionResult. * First gets all the {@link TargetDescriptor}s from the * {@link Descriptor}s list then creates {@link URLChecker} threads with * {@link TargetDescriptor}s and runs them. When all the threads have * finished execution, the {@link FunctionResult} is built and returned. * The returned {@link FunctionResult} contains all the links which were * checked with their results. * * @param descriptors * the list of {@link Descriptor}s defined in the runtime properties, * cannot be null. * * @return * the constructed {@link FunctionResult} object, never * null. * * @throws IllegalArgumentException * if descriptors == null. */ static FunctionResult checkLinks(List descriptors) throws IllegalArgumentException { // Check preconditions MandatoryArgumentChecker.check("descriptors", descriptors); List threads = new ArrayList(); if (descriptors.size() > 0) { // Get all the targets from the descriptor list List targetDescriptors = getTargetDescriptors(descriptors); // Create the thread for each target and run them threads = createAndRunUrlCheckers(targetDescriptors); // Get the biggest time-out from all the targets int timeout = getBiggestTimeout(targetDescriptors); // Wait till all the threads finish their execution or timedout. waitTillThreadsRunning(threads, timeout); // Confirm all threads have finished their execution. confirmThreadsStopped(threads); } // Start building the result FunctionResult builder = new FunctionResult(); int errorCount = (descriptors.size() > 0) ? addCheckElements(builder, threads) : 0; builder.param("linkCount", String.valueOf(threads.size())); builder.param("errorCount", String.valueOf(errorCount)); return builder; } /** * Creates a list of TargetDescriptors from the * given Descriptors list. Each {@link Descriptor} in the * list contains a list of {@link TargetDescriptor}s, which are added to * the returned list. * * @param descriptors * the list of {@link Descriptor}s, cannot be null. * * @return * the constructed {@link TargetDescriptor}s list, never * null. * * @throws IllegalArgumentException * if descriptors == null. */ private static List getTargetDescriptors(List descriptors) throws IllegalArgumentException { // Check preconditions MandatoryArgumentChecker.check("descriptors", descriptors); Iterator descriptorIterator = descriptors.iterator(); List targetDescriptors = new ArrayList(); // Each descriptor in the list contains target descriptors, so // iterate over descriptors and get all the target descriptors, then // iterate over each target descriptor and get the individual // target descriptors. while (descriptorIterator.hasNext()) { Descriptor descriptor = (Descriptor) descriptorIterator.next(); // Get the iterator on target descriptor Iterator targetIterator = descriptor.iterateTargets(); while (targetIterator.hasNext()) { TargetDescriptor targetDescriptor = (TargetDescriptor) targetIterator.next(); // Add all the target descriptors in a list targetDescriptors.add(targetDescriptor); } } return targetDescriptors; } /** * Creates and runs a thread for each TargetDescriptor in the * given list. Each {@link TargetDescriptor} in the list contains a URL. A * {@link URLChecker} thread is created for each {@link TargetDescriptor}, * which tries to connect to the URL provided in the * {@link TargetDescriptor}. Each thread is then added to a list which is * returned. * * @param targetDescriptors * the list of {@link TargetDescriptor}s which needs to be checked, * cannot be null. * * @return * the constructed {@link URLChecker}s list, never null. * * @throws IllegalArgumentException * if targetDescriptors == null. */ private static List createAndRunUrlCheckers(List targetDescriptors) throws IllegalArgumentException { // Check preconditions MandatoryArgumentChecker.check("targetDescriptors", targetDescriptors); // Iterate over all target descriptors List threads = new ArrayList(); Iterator targets = targetDescriptors.iterator(); while (targets.hasNext()) { TargetDescriptor target = (TargetDescriptor) targets.next(); // Create a thread for the target descriptor URLChecker urlThread = new URLChecker(target); // Start the thread with target descriptor urlThread.start(); // Store the thread just started in a list threads.add(urlThread); } return threads; } /** * Returns the biggest time-out of all the URLs defined in * TargetDescriptors list. Each {@link TargetDescriptor} in * the list has total time-out. The biggest of all of them is returned. * This time-out is then used to setup the time-outs of the * {@link URLChecker} threads. * * @param targetDescriptors * the list of {@link TargetDescriptor}s, cannot be null. * * @return * the biggest time-out from the list, or -1 if none of the * target descriptors defines a time-out. * * @throws IllegalArgumentException * if targetDescriptors == null. */ private static int getBiggestTimeout(List targetDescriptors) throws IllegalArgumentException { // Check preconditions MandatoryArgumentChecker.check("targetDescriptors", targetDescriptors); Iterator targets = targetDescriptors.iterator(); int biggestTimeout = -1; // Iterate over all target descriptors while (targets.hasNext()) { TargetDescriptor target = (TargetDescriptor) targets.next(); // Try to get the biggest time out of all the target descriptors if (biggestTimeout < target.getTotalTimeOut()) { biggestTimeout = target.getTotalTimeOut(); } } return biggestTimeout; } /** * Sets up the time-out for each thread and waits till each thread finishes * execution. The time-out is the biggest time-out of all the URLs in * {@link TargetDescriptor}s. Timeout for every next thread also considers * the time which is already spent and that time is subtracted from the * time-out for the current thread. * * @param threads * the list of {@link URLChecker} threads, cannot be null. * * @param timeout * the time-out for {@link URLChecker} threads. * * @throws IllegalArgumentException * if threads == null. */ private static void waitTillThreadsRunning(List threads, int timeout) throws IllegalArgumentException { // Check preconditions MandatoryArgumentChecker.check("threads", threads); Iterator threadIterator = threads.iterator(); long threadTimeout = timeout; // Storing the time approximately when the first thread was started long startTime = System.currentTimeMillis(); try { // Iterate over all the threads while (threadIterator.hasNext()) { URLChecker urlThread = (URLChecker) threadIterator.next(); urlThread.join(threadTimeout); // If the previous thread was setup with a certain time-out // the next thread should be setup with a time-out subtracted // by the time which is already passed. long endTime = System.currentTimeMillis(); long timePassed = endTime - startTime; threadTimeout = timeout - timePassed; // If the time-out becomes negative, it means that the total // time-out interval has passed now we do not need to setup // time-out for threads and they all should have finished // execution by now. if (threadTimeout <= 0) { return; } } } catch (InterruptedException exception) { // The exception is thrown when another thread has interrupted // the current thread. This should never happen so it should log // a programming error and throw a ProgrammingException. throw Utils.logProgrammingError(exception); } } /** * Confimrs that each URLChecker has finished its execution. * If some threads are still running, inforce a connection time-out and let * it run and ignore. * * @param threads * the list of {@link URLChecker} threads, cannot be null. * * @throws IllegalArgumentException * if threads == null. */ private static void confirmThreadsStopped(List threads) throws IllegalArgumentException { // Check preconditions MandatoryArgumentChecker.check("threads", threads); Iterator threadIterator = threads.iterator(); // Iterate over all the threads while (threadIterator.hasNext()) { URLChecker urlThread = (URLChecker) threadIterator.next(); // Check if thread is still alive. if (urlThread.isAlive()) { // Enforce a time-out for the thread and log it. urlThread.enforceTimeout(); Log.log_3505(urlThread.getURL()); } } } /** * Builds the FunctionResult for all the URLs checked. It * iterates over the list of all {@link URLChecker} threads and gets the * information like the total time each thread took to execute and the * result of the execution. The information is added in an * {@link ElementBuilder} object using which {@link org.xins.common.xml.Element} * is created which then is added to the passed {@link FunctionResult}. * * @param builder * the {@link FunctionResult} where the result is added, cannot be * null. * * @param threads * the list of {@link URLChecker} threads, cannot be null. * * @return * the total number of URLs without success. * * @throws IllegalArgumentException * if builder == null || threads == null. */ private static int addCheckElements(FunctionResult builder, List threads) throws IllegalArgumentException { // Check preconditions MandatoryArgumentChecker.check("builder", builder, "threads", threads); Iterator threadIterator = threads.iterator(); int errorCount = 0; // Iterate over the threads of target descriptors and create the // check element. while (threadIterator.hasNext()) { URLChecker urlThread = (URLChecker) threadIterator.next(); ElementBuilder eb = new ElementBuilder("check"); eb.setAttribute("url", urlThread.getURL()); eb.setAttribute("duration", Long.toString(urlThread.getDuration())); eb.setAttribute("result", getResult(urlThread)); builder.add(eb.createElement()); if (!urlThread.getSuccess()) { errorCount ++; } } return errorCount; } /** * Returns the value for the result parameter which is added in the * FunctionBuilder. The value of the result depends on the * success or failure of the passed {@link URLChecker} thread. If the * {@link URLChecker} thread gives a success, the status code of the * {@link URLChecker} thread is used to create the value for result * parameter, otherwise the exception in the {@link URLChecker} thread * determines the value for the result parameter. * * @param urlThread * the {@link URLChecker} thread for which the result value is to * detemined, cannot be null. * * @return * the result message, never null. * * @throws IllegalArgumentException * if urlThread == null || urlThread.hasRun() == false. */ private static String getResult(URLChecker urlThread) throws IllegalArgumentException { // Check preconditions MandatoryArgumentChecker.check("urlThread", urlThread); if (! urlThread.hasRun()) { throw new IllegalArgumentException("urlThread().hasRun() == false"); } if (urlThread.getSuccess()) { return SUCCESS; } else { return getResult(urlThread.getException(), urlThread.getURL()); } } /** * Returns the value for the result parameter which is added in the * FunctionBuilder when the URLChecker thread * failed to connect the URL. The value for the result parameter depends * on the exception occured in the {@link URLChecker} thread. The * exception is passed to this method. Based on the type of exception, an * appropriate value is returned. * * @param exception * the {@link Throwable} exception occured in the {@link URLChecker} * thread, cannot be null. * * @param url * the url which threw the exception, cannot be null. * * @return * the result message, never null. * * @throws IllegalArgumentException * if exception == null. */ private static String getResult(Throwable exception, String url) throws IllegalArgumentException { // Check preconditions. MandatoryArgumentChecker.check("exception", exception, "url", url); String exceptionName = exception.getClass().getName(); String result; // DNS error, unknown host name if (exception instanceof UnknownHostException) { result = UNKNOWN_HOST; // Connection time-out } else if (exceptionName.equals("org.apache.commons.httpclient.ConnectTimeoutException") || exception.getMessage().startsWith("Connect timed out")) { result = CONNECTION_TIMEOUT; // Connection refused } else if (exception instanceof ConnectException) { result = CONNECTION_REFUSAL; // SocketTimeoutException is not available in older Java versions, // so we do not refer to the class to avoid a NoClassDefFoundError. } else if (exceptionName.equals("java.net.SocketTimeoutException")) { result = SOCKET_TIMEOUT; // HTTPClient 2.0 socket time out is done using the HttpRecoverableException } else if (exception instanceof HttpRecoverableException && ((HttpRecoverableException) exception).getReason().indexOf("Read timed out") != -1) { result = SOCKET_TIMEOUT; // Interrupted I/O (this _may_ indicate a socket time-out) } else if (exception instanceof InterruptedIOException) { String exMessage = exception.getMessage(); // XXX: Only tested on Sun JVM // TODO: Test on non-Sun JVM if (exMessage.startsWith("Read timed out")) { result = SOCKET_TIMEOUT; // Unspecific I/O error } else { result = OTHER_IO_ERROR; } // Other I/O error } else if (exception instanceof IOException) { result = OTHER_IO_ERROR; // Other error, apparently not an I/O error } else { result = OTHER_FAILURE; } // Log the result and exception. Log.log_3502(exception, url, result); return result; } /** * Creates a new CheckLinks object. */ private CheckLinks() { // empty } /** * Tries to connect to a URL provided in the * TargetDescriptor. Runs as a separate thread. The URL is * connected by sending a request associated with an HTTP * OPTIONS method. Also calculates the total time to * connect to the provided URL. * *

The following example uses a {@link CheckLinks} object to get the * {@link FunctionResult}. * *

TargetDescriptor target = new TargetDescriptor();
    * target.setURL("www.hotmail.com");
    *
    * URLChecker urlThread = new URLChecker(target);
    * urlThread.start();
    *
    * String URL = urlThread.getURL();
    * int duration = urlThread.getDuration();
    * boolean success = urlThread.getSuccess();
    * if (!success) {
    *    exception = urlThread.getException();
    * }
* * @version $Revision: 1.37 $ $Date: 2007/05/22 11:13:26 $ * @author Tauseef Rehman */ private static final class URLChecker extends Thread { /** * The target descriptor for which the URL needs to be checked. Never * null. */ private final TargetDescriptor _targetDescriptor; /** * The URL to be checked. Never null. */ private final String _url; /** * The exception thrown when accessing the URL. Can be * null if the URLChecker has not run yet, or * if there was no error. */ private Throwable _exception; /** * The result of the URL check. Is true if the * URLChecker has run and was successful. If either of * these conditions is not met, then false. */ private boolean _success; /** * The time taken to check the URL. Initially -1. */ private long _duration; /** * The status code returned when the URL was called. Initially * -1, when the URLChecker was not run yet. */ private int _statusCode; /** * Constructs a new URLChecker for the specified target * descriptor. * * @param targetDescriptor * the {@link TargetDescriptor}, whose URL needs to be checked, * cannot be null. * * @throws IllegalArgumentException * if targetDescriptor == null. */ public URLChecker(TargetDescriptor targetDescriptor) throws IllegalArgumentException { // Check preconditions MandatoryArgumentChecker.check("targetDescriptor", targetDescriptor); // Initialize fields _targetDescriptor = targetDescriptor; _url = targetDescriptor.getURL(); _duration = -1; _statusCode = -1; // Check postconditions if (_url == null) { throw Utils.logProgrammingError("_url == null"); } } /** * Runs this thread. It tries to connect to the URL provided in the * {@link TargetDescriptor}. The URL is connected by sending a request * associated with an HTTP OPTIONS method. It also * calculates the total time to connect to the provided URL and saves * the exception in case an exception occurs. * * @throws IllegalStateException * if this URLChecker has already run. */ public void run() throws IllegalStateException { // Check preconditions if (hasRun()) { throw new IllegalStateException("This URLChecker for URL: " + _url + "has already run."); } // Logging the start of this thread. Log.log_3503(_url, _targetDescriptor.getTotalTimeOut(), _targetDescriptor.getConnectionTimeOut(), _targetDescriptor.getSocketTimeOut()); // Register current time, to compute total duration later long startTime = System.currentTimeMillis(); HttpMethodBase optionsMethod = null; try { HttpClient client = new HttpClient(); // Set the socket time-out for the URL. client.setTimeout(_targetDescriptor.getSocketTimeOut()); // Set the connection time-out for the URL. client.setHttpConnectionFactoryTimeout(_targetDescriptor.getConnectionTimeOut()); // Create a new OptionsMethod with the URL, this will represent // a request for information about the communication options // available on the request/response chain identified by the url. // This method allows the client to determine the options and/or // requirements associated with a resource, or the capabilities // of a server, without implying a resource action or initiating // a resource retrieval. optionsMethod = new OptionsMethod(_url); optionsMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, NO_RETRIES); // Execute the OptionsMethod. _statusCode = client.executeMethod(optionsMethod); // Successfully executed, so set the success as true. _success = true; } catch (Throwable exception) { // Save the exception and set the success as false as the // execution was failed. _exception = exception; _success = false; } finally { releaseConnection(optionsMethod); } // Calculate the total time taken to check the URL. _duration = System.currentTimeMillis() - startTime; // Logging the stopping of this thread. Log.log_3504(_url, _duration); } /** * Releases the connection used by the passed * HttpMethodBase. If the connection is not released * successfully and an exception is thrown, then it is just logged and * ignored. If the argument is null then nothing is done. * * @param method * the {@link HttpMethodBase} potentially having an unreleased * connection, can be null. */ private void releaseConnection(HttpMethodBase method) { if (method != null) { // Release the connection try { method.releaseConnection(); // Just ignore (and log) any exception as we do not want to fail // if the connection is not properly released. } catch (Throwable ignorable) { Utils.logIgnoredException(ignorable); } } } /** * Checks if this URLChecker has already run. * * @return * true if this URLChecker has already run, * or false otherwise. */ boolean hasRun() { return (_duration >= 0); } /** * Checks if this URLChecker has already run and if not, * throws an exception. * * @throws IllegalStateException * if this URLChecker has not run yet. */ private void assertHasRun() throws IllegalStateException { if (!hasRun()) { String message = "This URLChecker has not run yet. URL: \"" + _url + "\"."; throw new IllegalStateException(message); } } /** * Returns the total time it took to connect to the URL. * * @return * the total duration in milliseconds, or -1 if this * thread has not run. * * @throws IllegalStateException * if this URLChecker has not run yet. */ public long getDuration() throws IllegalStateException { assertHasRun(); return _duration; } /** * Returns the flag indicating if the URL was connected successfully. * * @return * the success flag, Is true if this thread has run and * was successful. If either of these conditions is not met, * then false. * * @throws IllegalStateException * if this URLChecker has not run yet. */ public boolean getSuccess() throws IllegalStateException { assertHasRun(); return _success; } /** * Returns the status code of the method execution. * * @return * the status code returned when the URL was called. -1, * when this thread has not run. * * @throws IllegalStateException * if this URLChecker has not run yet. */ public int getStatusCode() throws IllegalStateException { assertHasRun(); return _statusCode; } /** * Returns the URL which was connected. * * @return * the URL, never null. * * @throws IllegalStateException * if this URLChecker has not run yet. */ public String getURL() throws IllegalStateException { assertHasRun(); return _url; } /** * Returns the exception thrown while trying to connect to the URL. * * @return * the exception, can be null. * * @throws IllegalStateException * if this URLChecker has not run yet. */ public Throwable getException() throws IllegalStateException { assertHasRun(); return _exception; } /** * Enforces a time-out on the URLChecker thread. Actualy * the thread is allowed to run and ignored. So set the duration as the * initial connection time-out value and create a new * {@link ConnectException}. */ public void enforceTimeout() { if (! hasRun()) { // Set the duration as was defined for connection time-out _duration = _targetDescriptor.getConnectionTimeOut(); // Create a new ConnectException. _exception = new ConnectException("Connect timed out"); // XXX: Currently it is observed that mostly the URLs which are // expected to throw a ConnectTimeoutException keeps on running // but we need to take care of the situation when because of some // other reason the thread is still active. } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy