001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.core.service.wp;
028:
029: import java.io.Serializable;
030:
031: /**
032: * A request for the {@link WhitePagesService}.
033: * <p>
034: * Request objects are immutable. A client submits a request
035: * to the white pages and watches the mutable Response object.
036: */
037: public abstract class Request implements Serializable {
038:
039: /**
040: * The options flag to indicate no options.
041: */
042: public static final int NONE = 0;
043:
044: /**
045: * Flag to limit the operation to the local cache.
046: * <p>
047: * For "get", "getAll", and "list", a CACHE_ONLY flag limits the
048: * lookup to the local cache. If the result is not in the cache
049: * then the result will be set to the default value as defined
050: * below.
051: * <p>
052: * For "bind", this can be used to bootstrap entries into the local
053: * (client-side) "get" table. This can be used for both the local
054: * agents and remote agents discovered through non-WP mechanisms.
055: * If a future "get" request is not in the cache, then the hints
056: * are checked and will be used if present. A hint can be removed
057: * with an "unbind-hint".
058: */
059: public static final int CACHE_ONLY = (1 << 1);
060: // todo: recurse, authority-only, etc
061:
062: private final int options;
063:
064: private Request(int options) {
065: this .options = options;
066: }
067:
068: public final boolean hasOption(int mask) {
069: return ((options & mask) != 0);
070: }
071:
072: /**
073: * Create a response object for this request.
074: */
075: public abstract Response createResponse();
076:
077: public String toString() {
078: return " oid=" + System.identityHashCode(this ) + " cacheOnly="
079: + hasOption(CACHE_ONLY) + ")";
080: }
081:
082: /**
083: * Test to see if the specified name string is valid
084: * for use in Get, GetAll, Bind, and Unbind requests.
085: * <p>
086: * Clients should follow the Internet host name format
087: * (RFC 2396).
088: */
089: public static final boolean isValidName(String name) {
090: if (name == null || name.length() == 0
091: || !Character.isLetterOrDigit(name.charAt(0))) {
092: return false;
093: }
094: return true;
095: }
096:
097: /**
098: * Test to see if the specified suffix string is valid
099: * for use in List requests.
100: * <p>
101: * The suffix must start with '.'. For names other than '.',
102: * trailing '.'s will be ignored (e.g. ".x." is the same as
103: * ".x").
104: * <p>
105: * The name space is separated by using the "." character, just
106: * like internet host names (RFC 952). All children of a name
107: * must have a matching suffix, and only the direct (first-level)
108: * children will be listed.
109: * <p>
110: * For example, given:<pre>
111: * list(".foo.com")
112: * </pre>the result may look like be:<pre>
113: * { "www.foo.com", ".bar.foo.com" }
114: * </pre>where "www.foo.com" is an entry, and ".bar.foo.com" is a
115: * suffix for one or more child entries. If there were entries
116: * for both "www.foo.com" and subchild "test.www.foo.com", then both
117: * would be listed:<pre>
118: * { "www.foo.com", ".www.foo.com", ".bar.foo.com" }
119: * </pre>Note that listing ".foo.com" will not list the second
120: * level children, such as "test.www.foo.com". The client must
121: * do the <i>(non-scalable)</i> depth traversal itself.
122: * <p>
123: * The precise regex pattern is:<pre>
124: * new java.util.regex.Pattern(
125: * "^\.?[^\.]+" +
126: * suffix +
127: * "$");</pre>
128: */
129: public static final boolean isValidSuffix(String suffix) {
130: if (suffix == null || suffix.length() == 0
131: || suffix.charAt(0) != '.') {
132: return false;
133: }
134: return true;
135: }
136:
137: /**
138: * Get the entry with the matching (name, type) fields.
139: *
140: * @see Request.GetAll get all entries with a given name
141: */
142: public static final class Get extends Request {
143: private final String name;
144: private final String type;
145: private transient int _hc;
146:
147: public Get(int options, String name, String type) {
148: super (options);
149: this .name = name;
150: this .type = type;
151: if (!isValidName(name)) {
152: throw new IllegalArgumentException("Invalid name: "
153: + name);
154: }
155: if (type == null) {
156: throw new IllegalArgumentException("Null type");
157: }
158: }
159:
160: public String getName() {
161: return name;
162: }
163:
164: public String getType() {
165: return type;
166: }
167:
168: public Response createResponse() {
169: return new Response.Get(this );
170: }
171:
172: public int hashCode() {
173: if (_hc == 0) {
174: int h = 0;
175: h = 31 * h + name.hashCode();
176: h = 31 * h + type.hashCode();
177: _hc = h;
178: }
179: return _hc;
180: }
181:
182: public boolean equals(Object o) {
183: if (o == this ) {
184: return true;
185: } else if (!(o instanceof Get)) {
186: return false;
187: } else {
188: Get g = (Get) o;
189: return (name.equals(g.name) && type.equals(g.type));
190: }
191: }
192:
193: public String toString() {
194: return "(get name=" + getName() + " type=" + getType()
195: + super .toString();
196: }
197: }
198:
199: /**
200: * Get all entries associated with the given name.
201: *
202: * @see Request.Get do a specific (name, type) lookup
203: */
204: public static final class GetAll extends Request {
205: private final String name;
206:
207: public GetAll(int options, String name) {
208: super (options);
209: this .name = name;
210: if (!isValidName(name)) {
211: throw new IllegalArgumentException("Invalid name: "
212: + name);
213: }
214: }
215:
216: public String getName() {
217: return name;
218: }
219:
220: public Response createResponse() {
221: return new Response.GetAll(this );
222: }
223:
224: public boolean equals(Object o) {
225: if (o == this ) {
226: return true;
227: } else if (!(o instanceof GetAll)) {
228: return false;
229: } else {
230: return name.equals(((GetAll) o).name);
231: }
232: }
233:
234: public int hashCode() {
235: return name.hashCode();
236: }
237:
238: public String toString() {
239: return "(getAll name=" + getName() + super .toString();
240: }
241: }
242:
243: /**
244: * List the name of all direct children with the given suffix.
245: * <p>
246: * This is similar to a DNS zone transfer (AXFR) limited to depth=1.
247: */
248: public static final class List extends Request {
249: private final String suffix;
250:
251: public List(int options, String suffix) {
252: super (options);
253: if (!isValidSuffix(suffix)) {
254: throw new IllegalArgumentException("Invalid suffix: "
255: + suffix);
256: }
257: // trim tail '.'s
258: String suf = suffix;
259: int len = (suf == null ? 0 : suf.length());
260: while (--len > 0 && suf.charAt(len) == '.') {
261: suf = suf.substring(0, len);
262: }
263: this .suffix = suf;
264: }
265:
266: public String getSuffix() {
267: return suffix;
268: }
269:
270: public Response createResponse() {
271: return new Response.List(this );
272: }
273:
274: public boolean equals(Object o) {
275: if (o == this ) {
276: return true;
277: } else if (!(o instanceof List)) {
278: return false;
279: } else {
280: return suffix.equals(((List) o).suffix);
281: }
282: }
283:
284: public int hashCode() {
285: return suffix.hashCode();
286: }
287:
288: public String toString() {
289: return "(list suffix=" + getSuffix() + super .toString();
290: }
291: }
292:
293: /**
294: * Modifies the local white pages cache to remove or refetch
295: * cached "getAll" or "list" data.
296: * <p>
297: * This is always CACHE_ONLY, since it can only modify the
298: * local white pages cache.
299: * <p>
300: * The client must specify the name of the "getAll" entry (or
301: * equivalent "list" name suffix). One or more of the
302: * following optional assertions can also be specified:<ul>
303: * <li>The minimal age for the cached data in milliseconds,
304: * to avoid flushing new data.</li>
305: * <li>An AddressEntry that must be listed in the current
306: * cached "getAll" map of entries, to assert that the
307: * cache hasn't been update while the flush is in
308: * progress.</li>
309: * </ul>
310: * <p>
311: * If the above assertions are satisfied, one or more of these
312: * specified actions can be performed:<ul>
313: * <li>To <i>uncache</i> all entries associated with the name.
314: * This should only be done if the locally cached entry
315: * is certainly stale (e.g. due to information received
316: * from a third party)</li>
317: * <li>To <i>prefetch</i> in a background thread for updated
318: * cache data, without uncaching the maybe-stale cached
319: * entries. This should be used if the locally cached entry
320: * is usable for now but suspected to be stale (e.g. a
321: * message address used to work but is now unreachable).</li>
322: * </ul>
323: * Clients that use flush requests should carefully weigh the
324: * performance costs of local cache validity verses increased
325: * white pages message traffic.
326: */
327: public static final class Flush extends Request {
328:
329: private final String name;
330: private final long minAge;
331: private final AddressEntry ae;
332: private final boolean uncache;
333: private final boolean prefetch;
334:
335: public Flush(int options, String name, long minAge,
336: AddressEntry ae, boolean uncache, boolean prefetch) {
337: super (options);
338: this .name = name;
339: this .minAge = minAge;
340: this .ae = ae;
341: this .uncache = uncache;
342: this .prefetch = prefetch;
343: if (!hasOption(CACHE_ONLY)) {
344: throw new IllegalArgumentException(
345: "Flush must be \"CACHE_ONLY\"");
346: }
347: if (name == null || name.length() <= 0) {
348: throw new IllegalArgumentException("Invalid name: "
349: + name);
350: }
351: if (ae != null && !name.equals(ae.getName())) {
352: throw new IllegalArgumentException(
353: "Asserted AddressEntry name "
354: + ae.getName()
355: + " doesn't match the specified flush name: "
356: + name + " " + ae);
357: }
358: if (name.charAt(0) == '.' && ae != null) {
359: throw new IllegalArgumentException("List suffix "
360: + name + " can't specify an AddressEntry"
361: + " assertion " + ae);
362: }
363: if (!uncache && !prefetch) {
364: throw new IllegalArgumentException(
365: "Must specify either \"uncache\" and/or \"prefetch\"");
366: }
367: }
368:
369: public String getName() {
370: return name;
371: }
372:
373: public long getMinimumAge() {
374: return minAge;
375: }
376:
377: public AddressEntry getAddressEntry() {
378: return ae;
379: }
380:
381: public boolean isUncache() {
382: return uncache;
383: }
384:
385: public boolean isPrefetch() {
386: return prefetch;
387: }
388:
389: public Response createResponse() {
390: return new Response.Flush(this );
391: }
392:
393: public boolean equals(Object o) {
394: if (o == this ) {
395: return true;
396: } else if (!(o instanceof Flush)) {
397: return false;
398: } else {
399: Flush f = (Flush) o;
400: return (name.equals(f.name)
401: && minAge == f.minAge
402: && (ae == null ? f.ae == null : ae.equals(f.ae))
403: && (uncache == f.uncache) && (prefetch == f.prefetch));
404: }
405: }
406:
407: public int hashCode() {
408: return (name.hashCode() + (uncache ? 1 : 2) + (prefetch ? 3
409: : 4));
410: }
411:
412: public String toString() {
413: return "("
414: + (isUncache() ? ("uncache" + (isPrefetch() ? "+"
415: : "")) : "")
416: + (isPrefetch() ? "prefetch" : "")
417: + " "
418: + getName()
419: + (0 < getMinimumAge() ? (" minAge=" + getMinimumAge())
420: : "")
421: + (getAddressEntry() == null ? ""
422: : (" entry=" + getAddressEntry()))
423: + super .toString();
424: }
425: }
426:
427: /**
428: * Bind a new entry, or rebind an existing entry if the overwrite
429: * flag is false.
430: * <p>
431: * See the above notes on the CACHE_ONLY flag for binding
432: * client-side bootstrap "hints".
433: * <p>
434: * The renewal flag is for the infrastructure's use, for renewing
435: * bind leases.
436: */
437: public static final class Bind extends Request {
438:
439: private final AddressEntry ae;
440: private final boolean overWrite;
441: private final boolean renewal;
442:
443: public Bind(int options, AddressEntry ae, boolean overWrite,
444: boolean renewal) {
445: super (options);
446: this .ae = ae;
447: this .overWrite = overWrite;
448: this .renewal = renewal;
449: if (ae == null) {
450: throw new IllegalArgumentException("Null entry");
451: }
452: if (!isValidName(ae.getName())) {
453: // this could be moved into AddressEntry itself...
454: throw new IllegalArgumentException("Invalid name: "
455: + ae.getName());
456: }
457: if (renewal && (overWrite || hasOption(CACHE_ONLY))) {
458: throw new IllegalArgumentException(
459: "Renewal implies non-overwrite and non-cache-only");
460: }
461: }
462:
463: public AddressEntry getAddressEntry() {
464: return ae;
465: }
466:
467: public boolean isOverWrite() {
468: return overWrite;
469: }
470:
471: public boolean isRenewal() {
472: return renewal;
473: }
474:
475: public Response createResponse() {
476: return new Response.Bind(this );
477: }
478:
479: public boolean equals(Object o) {
480: if (o == this ) {
481: return true;
482: } else if (!(o instanceof Bind)) {
483: return false;
484: } else {
485: Bind b = (Bind) o;
486: return (ae.equals(b.ae) && overWrite == b.overWrite && renewal == b.renewal);
487: }
488: }
489:
490: public int hashCode() {
491: return (ae.hashCode() + (overWrite ? 1 : 2) + (renewal ? 3
492: : 4));
493: }
494:
495: public String toString() {
496: return "("
497: + (hasOption(CACHE_ONLY) ? "hint_" : "")
498: + (isOverWrite() ? "rebind" : isRenewal() ? "renew"
499: : "bind") + " entry=" + getAddressEntry()
500: + super .toString();
501: }
502: }
503:
504: /**
505: * Destroy the binding for the specified entry.
506: * <p>
507: * The client must pass the current value for the bound entry.
508: */
509: public static final class Unbind extends Request {
510: private final AddressEntry ae;
511:
512: public Unbind(int options, AddressEntry ae) {
513: super (options);
514: this .ae = ae;
515: if (ae == null) {
516: throw new IllegalArgumentException("Null entry");
517: }
518: if (!isValidName(ae.getName())) {
519: // this could be moved into AddressEntry itself...
520: throw new IllegalArgumentException("Invalid name: "
521: + ae.getName());
522: }
523: }
524:
525: public AddressEntry getAddressEntry() {
526: return ae;
527: }
528:
529: public Response createResponse() {
530: return new Response.Unbind(this );
531: }
532:
533: public boolean equals(Object o) {
534: if (o == this ) {
535: return true;
536: } else if (!(o instanceof Unbind)) {
537: return false;
538: } else {
539: Unbind u = (Unbind) o;
540: return ae.equals(u.ae);
541: }
542: }
543:
544: public int hashCode() {
545: return ae.hashCode();
546: }
547:
548: public String toString() {
549: return "(" + (hasOption(CACHE_ONLY) ? "unhint" : "unbind")
550: + " entry=" + getAddressEntry() + super.toString();
551: }
552: }
553: }
|