001: /*
002: * $Id: TargetDescriptor.java,v 1.62 2007/05/21 08:34:42 agoubard Exp $
003: *
004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
005: * See the COPYRIGHT file for redistribution and use restrictions.
006: */
007: package org.xins.common.service;
008:
009: import java.io.UnsupportedEncodingException;
010: import java.net.MalformedURLException;
011: import java.util.NoSuchElementException;
012: import java.util.zip.CRC32;
013:
014: import org.apache.oro.text.regex.Pattern;
015: import org.apache.oro.text.regex.Perl5Matcher;
016:
017: import org.xins.common.MandatoryArgumentChecker;
018: import org.xins.common.Utils;
019: import org.xins.common.text.HexConverter;
020: import org.xins.common.text.PatternUtils;
021:
022: /**
023: * Descriptor for a single target service. A target descriptor defines a URL
024: * that identifies the location of the service. Also, it may define 3 kinds of
025: * time-outs:
026: *
027: * <dl>
028: * <dt><em>total time-out</em> ({@link #getTotalTimeOut()})</dt>
029: * <dd>the maximum duration of a call, including connection time, time used
030: * to send the request, time used to receive the response, etc.</dd>
031: *
032: * <dt><em>connection time-out</em> ({@link #getConnectionTimeOut()})</dt>
033: * <dd>the maximum time for attempting to establish a connection.</dd>
034: *
035: * <dt><em>socket time-out</em> ({@link #getSocketTimeOut()})</dt>
036: * <dd>the maximum time for attempting to receive data on a socket.</dd>
037: * </dl>
038: *
039: * @version $Revision: 1.62 $ $Date: 2007/05/21 08:34:42 $
040: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
041: *
042: * @since XINS 1.0.0
043: */
044: public final class TargetDescriptor extends Descriptor {
045:
046: /**
047: * The number of instances of this class. Initially 0.
048: */
049: private static int INSTANCE_COUNT;
050:
051: /**
052: * The default time-out when no time-out is specified.
053: */
054: private static final int DEFAULT_TIMEOUT = 5000;
055:
056: /**
057: * The pattern for a URL, as a character string.
058: */
059: private static final String PATTERN_STRING = "[a-z][a-z\\d]*(:[a-z\\d]+)?:\\/\\/[a-z\\d-]*(\\.[a-z\\d-]*)*(:[1-9][\\d]*)?(\\/([a-z\\d%_~.-]*))*";
060:
061: /**
062: * The pattern for a URL.
063: */
064: private static Pattern PATTERN;
065:
066: /**
067: * Computes the CRC-32 checksum for the specified character string.
068: *
069: * @param s
070: * the string for which to compute the checksum, not <code>null</code>.
071: *
072: * @return
073: * the checksum for <code>s</code>.
074: */
075: private static int computeCRC32(String s) {
076:
077: // Compute the CRC-32 checksum
078: CRC32 checksum = new CRC32();
079: byte[] bytes;
080: final String ENCODING = "US-ASCII";
081: try {
082: bytes = s.getBytes(ENCODING);
083:
084: // Unsupported exception
085: } catch (UnsupportedEncodingException exception) {
086: throw Utils.logProgrammingError(exception);
087: }
088:
089: checksum.update(bytes, 0, bytes.length);
090: return (int) (checksum.getValue() & 0x00000000ffffffffL);
091: }
092:
093: /**
094: * The 1-based sequence number of this instance. Since this number is
095: * 1-based, the first instance of this class will have instance number 1
096: * assigned to it.
097: */
098: private final int _instanceNumber;
099:
100: /**
101: * A textual representation of this object. Lazily initialized by
102: * {@link #toString()} before returning it.
103: */
104: private String _asString;
105:
106: /**
107: * The URL for the service. Cannot be <code>null</code>.
108: */
109: private final String _url;
110:
111: /**
112: * The total time-out for the service. Is set to a 0 if no total time-out
113: * should be applied.
114: */
115: private final int _timeOut;
116:
117: /**
118: * The connection time-out for the service. Always greater than 0 and
119: * smaller than or equal to the total time-out.
120: */
121: private final int _connectionTimeOut;
122:
123: /**
124: * The socket time-out for the service. Always greater than 0 and smaller
125: * than or equal to the total time-out.
126: */
127: private final int _socketTimeOut;
128:
129: /**
130: * The CRC-32 checksum for the URL.
131: */
132: private final int _crc;
133:
134: /**
135: * Constructs a new <code>TargetDescriptor</code> for the specified URL.
136: *
137: * <p>Note: Both the connection time-out and the socket time-out will be
138: * set to the default time-out: 5 seconds.
139: *
140: * @param url
141: * the URL of the service, cannot be <code>null</code>.
142: *
143: * @throws IllegalArgumentException
144: * if <code>url == null</code>.
145: *
146: * @throws MalformedURLException
147: * if the specified URL is malformed.
148: */
149: public TargetDescriptor(String url)
150: throws IllegalArgumentException, MalformedURLException {
151: this (url, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT);
152: }
153:
154: /**
155: * Constructs a new <code>TargetDescriptor</code> for the specified URL,
156: * with the specifed total time-out.
157: *
158: * <p>Note: Both the connection time-out and the socket time-out will be
159: * set to equal the total time-out.
160: *
161: * @param url
162: * the URL of the service, cannot be <code>null</code>.
163: *
164: * @param timeOut
165: * the total time-out for the service, in milliseconds; or a
166: * non-positive value for no total time-out.
167: *
168: * @throws IllegalArgumentException
169: * if <code>url == null</code>.
170: *
171: * @throws MalformedURLException
172: * if the specified URL is malformed.
173: */
174: public TargetDescriptor(String url, int timeOut)
175: throws IllegalArgumentException, MalformedURLException {
176: this (url, timeOut, timeOut, timeOut);
177: }
178:
179: /**
180: * Constructs a new <code>TargetDescriptor</code> for the specified URL,
181: * with the specifed total time-out and connection time-out.
182: *
183: * <p>Note: If the passed connection time-out is smaller than 1 ms, or
184: * greater than the total time-out, then it will be adjusted to equal the
185: * total time-out.
186: *
187: * <p>Note: The socket time-out will be set to equal the total time-out.
188: *
189: * @param url
190: * the URL of the service, cannot be <code>null</code>.
191: *
192: * @param timeOut
193: * the total time-out for the service, in milliseconds; or a
194: * non-positive value for no total time-out.
195: *
196: * @param connectionTimeOut
197: * the connection time-out for the service, in milliseconds; or a
198: * non-positive value if the connection time-out should equal the total
199: * time-out.
200: *
201: * @throws IllegalArgumentException
202: * if <code>url == null</code>.
203: *
204: * @throws MalformedURLException
205: * if the specified URL is malformed.
206: */
207: public TargetDescriptor(String url, int timeOut,
208: int connectionTimeOut) throws IllegalArgumentException,
209: MalformedURLException {
210: this (url, timeOut, connectionTimeOut, timeOut);
211: }
212:
213: /**
214: * Constructs a new <code>TargetDescriptor</code> for the specified URL,
215: * with the specifed total time-out, connection time-out and socket
216: * time-out.
217: *
218: * <p>Note: If the passed connection time-out is smaller than 1 ms, or
219: * greater than the total time-out, then it will be adjusted to equal the
220: * total time-out.
221: *
222: * <p>Note: If the passed socket time-out is smaller than 1 ms or greater
223: * than the total time-out, then it will be adjusted to equal the total
224: * time-out.
225: *
226: * @param url
227: * the URL of the service, cannot be <code>null</code>.
228: *
229: * @param timeOut
230: * the total time-out for the service, in milliseconds; or a
231: * non-positive value for no total time-out.
232: *
233: * @param connectionTimeOut
234: * the connection time-out for the service, in milliseconds; or a
235: * non-positive value if the connection time-out should equal the total
236: * time-out.
237: *
238: * @param socketTimeOut
239: * the socket time-out for the service, in milliseconds; or a
240: * non-positive value for no socket time-out.
241: *
242: * @throws IllegalArgumentException
243: * if <code>url == null</code>.
244: *
245: * @throws MalformedURLException
246: * if the specified URL is malformed.
247: */
248: public TargetDescriptor(String url, int timeOut,
249: int connectionTimeOut, int socketTimeOut)
250: throws IllegalArgumentException, MalformedURLException {
251:
252: // Determine instance number first
253: _instanceNumber = ++INSTANCE_COUNT;
254:
255: // Check preconditions
256: MandatoryArgumentChecker.check("url", url);
257:
258: if (PATTERN == null) {
259: PATTERN = PatternUtils.createPattern(PATTERN_STRING);
260: }
261: Perl5Matcher patternMatcher = new Perl5Matcher();
262: if (!patternMatcher.matches(url, PATTERN)) {
263: throw new MalformedURLException(url);
264: }
265:
266: // Convert negative total time-out to 0
267: timeOut = (timeOut > 0) ? timeOut : 0;
268:
269: // If connection time-out or socket time-out is not set, then set it to
270: // the total time-out
271: connectionTimeOut = (connectionTimeOut > 0) ? connectionTimeOut
272: : timeOut;
273: socketTimeOut = (socketTimeOut > 0) ? socketTimeOut : timeOut;
274:
275: // If either connection or socket time-out is greater than total
276: // time-out, then limit it to the total time-out
277: connectionTimeOut = (connectionTimeOut < timeOut) ? connectionTimeOut
278: : timeOut;
279: socketTimeOut = (socketTimeOut < timeOut) ? socketTimeOut
280: : timeOut;
281:
282: // Set fields
283: _url = url;
284: _timeOut = timeOut;
285: _connectionTimeOut = connectionTimeOut;
286: _socketTimeOut = socketTimeOut;
287: _crc = computeCRC32(url);
288:
289: // NOTE: _asString is lazily initialized
290: }
291:
292: /**
293: * Checks if this descriptor denotes a group of descriptors.
294: *
295: * @return
296: * <code>false</code>, since this descriptor does not denote a group.
297: */
298: public boolean isGroup() {
299: return false;
300: }
301:
302: /**
303: * Returns the URL for the service.
304: *
305: * @return
306: * the URL for the service, not <code>null</code>.
307: */
308: public String getURL() {
309: return _url;
310: }
311:
312: /**
313: * Returns the protocol in the URL for the service.
314: *
315: * @return
316: * the protocol in the URL, not <code>null</code>.
317: *
318: * @since XINS 1.2.0
319: */
320: public String getProtocol() {
321: int index = _url.indexOf("://");
322: return _url.substring(0, index);
323: }
324:
325: /**
326: * Returns the total time-out for a call to the service. The value 0
327: * is returned if there is no total time-out.
328: *
329: * @return
330: * the total time-out for the service, as a positive number, in
331: * milli-seconds, or 0 if there is no total time-out.
332: */
333: public int getTotalTimeOut() {
334: return _timeOut;
335: }
336:
337: /**
338: * Returns the connection time-out for a call to the service.
339: *
340: * @return
341: * the connection time-out for the service; always greater than 0 and
342: * smaller than or equal to the total time-out.
343: */
344: public int getConnectionTimeOut() {
345: return _connectionTimeOut;
346: }
347:
348: /**
349: * Returns the socket time-out for a call to the service.
350: *
351: * @return
352: * the socket time-out for the service; always greater than 0 and
353: * smaller than or equal to the total time-out.
354: */
355: public int getSocketTimeOut() {
356: return _socketTimeOut;
357: }
358:
359: /**
360: * Returns the CRC-32 checksum for the URL of this target descriptor.
361: *
362: * @return
363: * the CRC-32 checksum.
364: */
365: public int getCRC() {
366: return _crc;
367: }
368:
369: /**
370: * Iterates over all leaves, the target descriptors.
371: *
372: * <p>The returned {@link java.util.Iterator} will only return this target
373: * descriptor.
374: *
375: * @return
376: * iterator that returns this target descriptor, never
377: * <code>null</code>.
378: */
379: public java.util.Iterator iterateTargets() {
380: return new Iterator();
381: }
382:
383: /**
384: * Counts the total number of target descriptors in/under this descriptor.
385: *
386: * @return
387: * the total number of target descriptors, always 1.
388: */
389: public int getTargetCount() {
390: return 1;
391: }
392:
393: /**
394: * Returns the <code>TargetDescriptor</code> that matches the specified
395: * CRC-32 checksum.
396: *
397: * @param crc
398: * the CRC-32 checksum.
399: *
400: * @return
401: * the {@link TargetDescriptor} that matches the specified checksum, or
402: * <code>null</code>, if none could be found in this descriptor.
403: */
404: public TargetDescriptor getTargetByCRC(int crc) {
405: return (_crc == crc) ? this : null;
406: }
407:
408: /**
409: * Returns a hash code value for the object.
410: *
411: * @return
412: * a hash code value for this object.
413: *
414: * @see Object#hashCode()
415: * @see #equals(Object)
416: */
417: public int hashCode() {
418: return _crc;
419: }
420:
421: /**
422: * Indicates whether some other object is "equal to" this one. This method
423: * considers <code>obj</code> equals if and only if it matches the
424: * following conditions:
425: *
426: * <ul>
427: * <li><code>obj instanceof TargetDescriptor</code>
428: * <li>URL is equal
429: * <li>total time-out is equal
430: * <li>connection time-out is equal
431: * <li>socket time-out is equal
432: * </ul>
433: *
434: * @param obj
435: * the reference object with which to compare.
436: *
437: * @return
438: * <code>true</code> if this object is the same as the <code>obj</code>
439: * argument; <code>false</code> otherwise.
440: *
441: * @see #hashCode()
442: */
443: public boolean equals(Object obj) {
444:
445: boolean equal = false;
446:
447: if (obj instanceof TargetDescriptor) {
448: TargetDescriptor that = (TargetDescriptor) obj;
449: equal = (_url.equals(that._url))
450: && (_timeOut == that._timeOut)
451: && (_connectionTimeOut == that._connectionTimeOut)
452: && (_socketTimeOut == that._socketTimeOut);
453: }
454:
455: return equal;
456: }
457:
458: /**
459: * Textual description of this object. The string includes the URL and all
460: * time-out values. For example:
461: *
462: * <blockquote><code>TargetDescriptor(url="http://api.google.com/some_api/";
463: * total-time-out is 5300 ms;
464: * connection time-out is 1000 ms;
465: * socket time-out is disabled)</code></blockquote>
466: *
467: * @return
468: * this <code>TargetDescriptor</code> as a {@link String}, never
469: * <code>null</code>.
470: */
471: public String toString() {
472:
473: // Lazily initialize
474: if (_asString == null) {
475: StringBuffer buffer = new StringBuffer(233);
476: buffer.append("TargetDescriptor #");
477: buffer.append(_instanceNumber);
478: buffer.append(" [url=\"");
479: buffer.append(_url);
480: buffer.append("\"; crc=\"");
481: buffer.append(HexConverter.toHexString(_crc));
482: buffer.append("\"; total time-out is ");
483: if (_timeOut < 1) {
484: buffer.append("disabled; connection time-out is ");
485: } else {
486: buffer.append(_timeOut);
487: buffer.append(" ms; connection time-out is ");
488: }
489: if (_connectionTimeOut < 1) {
490: buffer.append("disabled; socket time-out is ");
491: } else {
492: buffer.append(_connectionTimeOut);
493: buffer.append(" ms; socket time-out is ");
494: }
495: if (_socketTimeOut < 1) {
496: buffer.append("disabled]");
497: } else {
498: buffer.append(_socketTimeOut);
499: buffer.append(" ms]");
500: }
501: _asString = buffer.toString();
502: }
503:
504: return _asString;
505: }
506:
507: /**
508: * Iterator over this (single) target descriptor. Needed for the
509: * implementation of {@link #iterateTargets()}.
510: *
511: * @version $Revision: 1.62 $ $Date: 2007/05/21 08:34:42 $
512: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
513: *
514: * @since XINS 1.0.0
515: */
516: private final class Iterator implements java.util.Iterator {
517: /**
518: * Constructs a new <code>Iterator</code>.
519: */
520: private Iterator() {
521:
522: // empty
523: }
524:
525: /**
526: * Flag that indicates if this iterator is already done iterating over
527: * the single element.
528: */
529: private boolean _done;
530:
531: /**
532: * Checks if there is a next element.
533: *
534: * @return
535: * <code>true</code> if there is a next element, <code>false</code>
536: * if there is not.
537: */
538: public boolean hasNext() {
539: return !_done;
540: }
541:
542: /**
543: * Returns the next element.
544: *
545: * @return
546: * the next element, never <code>null</code>.
547: *
548: * @throws NoSuchElementException
549: * if there is no new element.
550: */
551: public Object next() throws NoSuchElementException {
552: if (_done) {
553: throw new NoSuchElementException();
554: } else {
555: _done = true;
556: return TargetDescriptor.this ;
557: }
558: }
559:
560: /**
561: * Removes the element last returned by <code>next()</code> (unsupported
562: * operation).
563: *
564: * @throws UnsupportedOperationException
565: * always thrown, since this operation is unsupported.
566: */
567: public void remove() throws UnsupportedOperationException {
568: throw new UnsupportedOperationException();
569: }
570: }
571: }
|