| TargetDescriptor.java |
/*
* $Id: TargetDescriptor.java,v 1.70 2012/03/03 21:23:44 agoubard Exp $
*
* See the COPYRIGHT file for redistribution and use restrictions.
*/
package org.xins.common.service;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.CRC32;
import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.Utils;
import org.xins.common.text.HexConverter;
import org.xins.common.text.PatternUtils;
/**
* Descriptor for a single target service. A target descriptor defines a URL
* that identifies the location of the service. Also, it may define 3 kinds of
* time-outs:
*
* <dl>
* <dt><em>total time-out</em> ({@link #getTotalTimeOut()})</dt>
* <dd>the maximum duration of a call, including connection time, time used
* to send the request, time used to receive the response, etc.</dd>
*
* <dt><em>connection time-out</em> ({@link #getConnectionTimeOut()})</dt>
* <dd>the maximum time for attempting to establish a connection.</dd>
*
* <dt><em>socket time-out</em> ({@link #getSocketTimeOut()})</dt>
* <dd>the maximum time for attempting to receive data on a socket.</dd>
* </dl>
*
* @version $Revision: 1.70 $ $Date: 2012/03/03 21:23:44 $
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
*
* @since XINS 1.0.0
*/
public final class TargetDescriptor extends Descriptor {
/**
* The number of instances of this class. Initially 0.
*/
private static int INSTANCE_COUNT;
/**
* The default time-out when no time-out is specified.
*/
private static final int DEFAULT_TIMEOUT = 5000;
/**
* The pattern for a URL, as a character string.
*/
private static final String PATTERN_STRING = "[a-z][a-z\\d]*(:[a-z\\d]+)*:\\/(\\/)?" +
"([\\w%~\\.\\-]+(:[\\w%~\\.\\-]+)?@)?" +
"[a-z\\d-]*(\\.[a-z\\d-]*)*(:[1-9][\\d]*)?" +
"(\\/([a-zA-Z\\d%_~\\.\\-]*))*" +
"(\\?([\\w%~\\.\\-]+=[\\w%~\\.\\-]*&?)*)?" +
"(#[\\w%~\\.\\-]*)?";
/**
* The pattern for a URL.
*/
private static Pattern PATTERN;
/**
* Computes the CRC-32 checksum for the specified character string.
*
* @param s
* the string for which to compute the checksum, not <code>null</code>.
*
* @return
* the checksum for <code>s</code>.
*/
private static int computeCRC32(String s) {
// Compute the CRC-32 checksum
CRC32 checksum = new CRC32();
byte[] bytes;
final String ENCODING = "US-ASCII";
try {
bytes = s.getBytes(ENCODING);
// Unsupported exception
} catch (UnsupportedEncodingException exception) {
throw Utils.logProgrammingError(exception);
}
checksum.update(bytes, 0, bytes.length);
return (int) (checksum.getValue() & 0x00000000ffffffffL);
}
/**
* The 1-based sequence number of this instance. Since this number is
* 1-based, the first instance of this class will have instance number 1
* assigned to it.
*/
private final int _instanceNumber;
/**
* A textual representation of this object. Lazily initialized by
* {@link #toString()} before returning it.
*/
private String _asString;
/**
* The URL for the service. Cannot be <code>null</code>.
*/
private final String _url;
/**
* The total time-out for the service. Is set to a 0 if no total time-out
* should be applied.
*/
private final int _timeOut;
/**
* The connection time-out for the service. Always greater than 0 and
* smaller than or equal to the total time-out.
*/
private final int _connectionTimeOut;
/**
* The socket time-out for the service. Always greater than 0 and smaller
* than or equal to the total time-out.
*/
private final int _socketTimeOut;
/**
* The CRC-32 checksum for the URL.
*/
private final int _crc;
/**
* Constructs a new <code>TargetDescriptor</code> for the specified URL.
*
* <p>Note: Both the connection time-out and the socket time-out will be
* set to the default time-out: 5 seconds.
*
* @param url
* the URL of the service, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>url == null</code>.
*
* @throws MalformedURLException
* if the specified URL is malformed.
*/
public TargetDescriptor(String url)
throws IllegalArgumentException, MalformedURLException {
this(url, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT);
}
/**
* Constructs a new <code>TargetDescriptor</code> for the specified URL,
* with the specifed total time-out.
*
* <p>Note: Both the connection time-out and the socket time-out will be
* set to equal the total time-out.
*
* @param url
* the URL of the service, cannot be <code>null</code>.
*
* @param timeOut
* the total time-out for the service, in milliseconds; or a
* non-positive value for no total time-out.
*
* @throws IllegalArgumentException
* if <code>url == null</code>.
*
* @throws MalformedURLException
* if the specified URL is malformed.
*/
public TargetDescriptor(String url, int timeOut)
throws IllegalArgumentException, MalformedURLException {
this(url, timeOut, timeOut, timeOut);
}
/**
* Constructs a new <code>TargetDescriptor</code> for the specified URL,
* with the specifed total time-out and connection time-out.
*
* <p>Note: If the passed connection time-out is smaller than 1 ms, or
* greater than the total time-out, then it will be adjusted to equal the
* total time-out.
*
* <p>Note: The socket time-out will be set to equal the total time-out.
*
* @param url
* the URL of the service, cannot be <code>null</code>.
*
* @param timeOut
* the total time-out for the service, in milliseconds; or a
* non-positive value for no total time-out.
*
* @param connectionTimeOut
* the connection time-out for the service, in milliseconds; or a
* non-positive value if the connection time-out should equal the total
* time-out.
*
* @throws IllegalArgumentException
* if <code>url == null</code>.
*
* @throws MalformedURLException
* if the specified URL is malformed.
*/
public TargetDescriptor(String url, int timeOut, int connectionTimeOut)
throws IllegalArgumentException, MalformedURLException {
this(url, timeOut, connectionTimeOut, timeOut);
}
/**
* Constructs a new <code>TargetDescriptor</code> for the specified URL,
* with the specifed total time-out, connection time-out and socket
* time-out.
*
* <p>Note: If the passed connection time-out is smaller than 1 ms, or
* greater than the total time-out, then it will be adjusted to equal the
* total time-out.
*
* <p>Note: If the passed socket time-out is smaller than 1 ms or greater
* than the total time-out, then it will be adjusted to equal the total
* time-out.
*
* @param url
* the URL of the service, cannot be <code>null</code>.
*
* @param timeOut
* the total time-out for the service, in milliseconds; or a
* non-positive value for no total time-out.
*
* @param connectionTimeOut
* the connection time-out for the service, in milliseconds; or a
* non-positive value if the connection time-out should equal the total
* time-out.
*
* @param socketTimeOut
* the socket time-out for the service, in milliseconds; or a
* non-positive value for no socket time-out.
*
* @throws IllegalArgumentException
* if <code>url == null</code>.
*
* @throws MalformedURLException
* if the specified URL is malformed.
*/
public TargetDescriptor(String url,
int timeOut,
int connectionTimeOut,
int socketTimeOut)
throws IllegalArgumentException, MalformedURLException {
// Determine instance number first
_instanceNumber = ++INSTANCE_COUNT;
// Check preconditions
MandatoryArgumentChecker.check("url", url);
if (PATTERN == null) {
PATTERN = PatternUtils.createPattern(PATTERN_STRING);
}
Matcher patternMatcher = PATTERN.matcher(url);
if (! patternMatcher.matches()) {
throw new MalformedURLException(url);
}
// Convert negative total time-out to 0
timeOut = (timeOut > 0) ? timeOut : 0;
// If connection time-out or socket time-out is not set, then set it to
// the total time-out
connectionTimeOut = (connectionTimeOut > 0) ? connectionTimeOut : timeOut;
socketTimeOut = (socketTimeOut > 0) ? socketTimeOut : timeOut;
// If either connection or socket time-out is greater than total
// time-out, then limit it to the total time-out
connectionTimeOut = (connectionTimeOut < timeOut) ? connectionTimeOut : timeOut;
socketTimeOut = (socketTimeOut < timeOut) ? socketTimeOut : timeOut;
// Set fields
_url = url;
_timeOut = timeOut;
_connectionTimeOut = connectionTimeOut;
_socketTimeOut = socketTimeOut;
_crc = computeCRC32(url);
// NOTE: _asString is lazily initialized
}
/**
* Checks if this descriptor denotes a group of descriptors.
*
* @return
* <code>false</code>, since this descriptor does not denote a group.
*/
public boolean isGroup() {
return false;
}
/**
* Returns the URL for the service.
*
* @return
* the URL for the service, not <code>null</code>.
*/
public String getURL() {
return _url;
}
/**
* Returns the protocol in the URL for the service.
*
* @return
* the protocol in the URL, not <code>null</code>.
*
* @since XINS 1.2.0
*/
public String getProtocol() {
int index = _url.indexOf(":/");
return _url.substring(0, index);
}
/**
* Returns the total time-out for a call to the service. The value 0
* is returned if there is no total time-out.
*
* @return
* the total time-out for the service, as a positive number, in
* milli-seconds, or 0 if there is no total time-out.
*/
public int getTotalTimeOut() {
return _timeOut;
}
/**
* Returns the connection time-out for a call to the service.
*
* @return
* the connection time-out for the service; always greater than 0 and
* smaller than or equal to the total time-out.
*/
public int getConnectionTimeOut() {
return _connectionTimeOut;
}
/**
* Returns the socket time-out for a call to the service.
*
* @return
* the socket time-out for the service; always greater than 0 and
* smaller than or equal to the total time-out.
*/
public int getSocketTimeOut() {
return _socketTimeOut;
}
/**
* Returns the CRC-32 checksum for the URL of this target descriptor.
*
* @return
* the CRC-32 checksum.
*/
public int getCRC() {
return _crc;
}
/**
* Iterates over all leaves, the target descriptors.
*
* <p>The returned {@link java.util.Iterator} will only return this target
* descriptor.
*
* @return
* iterator that returns this target descriptor, never
* <code>null</code>.
*/
public java.util.Iterator iterateTargets() {
return new Iterator();
}
public java.util.Iterator<TargetDescriptor> iterator() {
return new Iterator();
}
/**
* Counts the total number of target descriptors in/under this descriptor.
*
* @return
* the total number of target descriptors, always 1.
*/
public int getTargetCount() {
return 1;
}
/**
* Returns the <code>TargetDescriptor</code> that matches the specified
* CRC-32 checksum.
*
* @param crc
* the CRC-32 checksum.
*
* @return
* the {@link TargetDescriptor} that matches the specified checksum, or
* <code>null</code>, if none could be found in this descriptor.
*/
public TargetDescriptor getTargetByCRC(int crc) {
return (_crc == crc) ? this : null;
}
/**
* Returns a hash code value for the object.
*
* @return
* a hash code value for this object.
*
* @see Object#hashCode()
* @see #equals(Object)
*/
public int hashCode() {
return _crc;
}
/**
* Indicates whether some other object is "equal to" this one. This method
* considers <code>obj</code> equals if and only if it matches the
* following conditions:
*
* <ul>
* <li><code>obj instanceof TargetDescriptor</code>
* <li>URL is equal
* <li>total time-out is equal
* <li>connection time-out is equal
* <li>socket time-out is equal
* </ul>
*
* @param obj
* the reference object with which to compare.
*
* @return
* <code>true</code> if this object is the same as the <code>obj</code>
* argument; <code>false</code> otherwise.
*
* @see #hashCode()
*/
public boolean equals(Object obj) {
boolean equal = false;
if (obj instanceof TargetDescriptor) {
TargetDescriptor that = (TargetDescriptor) obj;
equal = (_url.equals(that._url))
&& (_timeOut == that._timeOut)
&& (_connectionTimeOut == that._connectionTimeOut)
&& (_socketTimeOut == that._socketTimeOut);
}
return equal;
}
/**
* Textual description of this object. The string includes the URL and all
* time-out values. For example:
*
* <blockquote><code>TargetDescriptor(url="http://api.google.com/some_api/";
* total-time-out is 5300 ms;
* connection time-out is 1000 ms;
* socket time-out is disabled)</code></blockquote>
*
* @return
* this <code>TargetDescriptor</code> as a {@link String}, never
* <code>null</code>.
*/
public String toString() {
// Lazily initialize
if (_asString == null) {
StringBuffer buffer = new StringBuffer(233);
buffer.append("TargetDescriptor #");
buffer.append(_instanceNumber);
buffer.append(" [url=\"");
buffer.append(_url);
buffer.append("\"; crc=\"");
buffer.append(HexConverter.toHexString(_crc));
buffer.append("\"; total time-out is ");
if (_timeOut < 1) {
buffer.append("disabled; connection time-out is ");
} else {
buffer.append(_timeOut);
buffer.append(" ms; connection time-out is ");
}
if (_connectionTimeOut < 1) {
buffer.append("disabled; socket time-out is ");
} else {
buffer.append(_connectionTimeOut);
buffer.append(" ms; socket time-out is ");
}
if (_socketTimeOut < 1) {
buffer.append("disabled]");
} else {
buffer.append(_socketTimeOut);
buffer.append(" ms]");
}
_asString = buffer.toString();
}
return _asString;
}
/**
* Iterator over this (single) target descriptor. Needed for the
* implementation of {@link #iterateTargets()}.
*
* @version $Revision: 1.70 $ $Date: 2012/03/03 21:23:44 $
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
*
* @since XINS 1.0.0
*/
private class Iterator implements java.util.Iterator {
/**
* Constructs a new <code>Iterator</code>.
*/
private Iterator() {
// empty
}
/**
* Flag that indicates if this iterator is already done iterating over
* the single element.
*/
private boolean _done;
/**
* Checks if there is a next element.
*
* @return
* <code>true</code> if there is a next element, <code>false</code>
* if there is not.
*/
public boolean hasNext() {
return ! _done;
}
/**
* Returns the next element.
*
* @return
* the next element, never <code>null</code>.
*
* @throws NoSuchElementException
* if there is no new element.
*/
public Object next() throws NoSuchElementException {
if (_done) {
throw new NoSuchElementException();
} else {
_done = true;
return TargetDescriptor.this;
}
}
/**
* Removes the element last returned by <code>next()</code> (unsupported
* operation).
*
* @throws UnsupportedOperationException
* always thrown, since this operation is unsupported.
*/
public void remove() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
}
}